1. ホーム

C++におけるconst_castの役割と理由

2022-02-09 05:11:43

C++標準の変換演算子const_cast

前回までのあらすじ C++はCのレガシークラスを継承・拡張する C++はCに比べてオブジェクト指向の言語である。オブジェクト指向言語の最大の特徴は、"を持つことである。 ポリモーフィズム となります。

ポリモーフィズムをうまく使うには、どうしてもポインタや参照を使う必要があり、変換の問題にぶつかることが多いので、今回は、師匠に言われたこと、インターネットで繰り返し調べてわかったことをまとめます。

C++では、4つの変換演算子が用意されています。

これらは同じ構造を持ち、テンプレートメソッドのように見えます。これらは、ポインタと参照の変換を行うために開発者に提供されるメソッドです。

ずっと書こうと思っていたことがあるんです。師匠から送られてきた資料を見たり、ネットで読んだりしていたのですが、C++の変換演算子の使い方を完全に理解するのが遅かったので、それらの資料を読み、まずは従来の変換を書きました。もし、誤解や間違いがあれば、先代や後代の方に訂正していただければと思います。

この標準演算子の目的は、従来の演算子を置き換えて統一することにあるような気がするのですが。ちょうど、' \n' の代わりに std::endl を使って改行を出力するようにね。これらの標準演算子で、対応する従来の変換がどのように行えるかをコードで説明します。もちろん、これは一般的な理解であって、コンパイラは標準演算子、特に従来の変換では完全には実装できない dynamic_cast についてもっと処理をしたはずである。

今回は、まずconst_cast演算子についての理解からお話しします。

const_cast (式)

const_cast コンバータは、変数から const または volatile 修飾子を削除するために使用されます。後者については、マルチスレッド設計に関わることなので、私はあまりよく知らないんだ。というわけで、今回はconst側についてだけお話します。

const_cast を使って const qualification を削除する

const変数では、その値を変更することはできません。これは、この修飾子の最も単純な現れです。しかし、その修飾を破って、中身を変更したい場合はどうすればいいのでしょうか?

次のようなコードでは、明らかにうまくいきません。  const int constant = 10;

int modifier = constant;

修飾子の変更は定数に影響を与えないので、これは1つのことを意味します:const_castコンバータはオブジェクトデータにも使用すべきではありません、なぜならそのような変換によって得られる2つの変数/オブジェクトは関連していないからです。

唯一の解決策は、ポインタや参照を使用して、変数が同じアドレスを指すようにすることですが、残念ながら以下のコードもC++ではコンパイルに失敗します。  const int constant = 21;

int* modifier = &constant 

// Error: invalid conversion from 'const int*' to 'int*'

(上記のコードはC言語でもコンパイルできますが、せいぜい警告が出る程度で、C言語では前のステップで定数内のデータをいじり始めることができます)

定数をconstでない参照に与えることもうまくいきません。  const int constant = 21;

int& modifier = constant;

// Error: invalid initialization of reference of type 'int&' from expression of type 'const int'

そこで、const_castが登場し、プログラミング界に混乱をもたらすためにconstを一掃しました。

次のコードはスムーズにコンパイルされ、合格に値するものです。  const int constant = 21;

const int* const_p = &constant;

int* modifier = const_cast<int*>(const_p);

*modifier = 7;

const_cast 演算子を実装するための従来の変換子

先ほど言ったように、標準:準変換演算子は従来の変換を利用して実装することができます。C++はポインタの変換が任意で、型チェックもせず、どんなポインタも相互に変換できるので、const_castは表示変換(int*)を利用すればよいので、実装されています。  const int constant = 21;

const int* const_p = &constant;

int* modifier = (int*)(const_p);

また、中間変数を省略して、次のように1つの文にまとめることもできます。  const int constant = 21;

int* modifier = (int*)(&constant);

代用品  const int constant = 21;

int* modifier = const_cast<int*>(&constant);

const修飾を削除する理由

前のコードからわかるように、定数に変更を加えることはできませんが、モディファイアを再割り当てすることは可能です。

だがしかし、プログラミングの世界は本当にメチャクチャなのだろうか?本当にモディファイアでconstatnの値を変更したのだろうか?const変数のデータを修正することが、本当にC++のde-constの目的なのだろうか?

その結果をプリントアウトすると  cout << "constant: "<< constant <<endl;

cout << "const_p: "<< *const_p <<endl;

cout << "modifier: "<< *modifier <<endl;

/**

constant: 21

const_p: 7

modifier: 7

**/

定数は、元の値を保持しています。

しかし、それらは同じアドレスを指しています。
cout << "constant: "<< &constant <<endl;

cout << "const_p: "<<< const_p <<endl;

cout << "modifier: "<<< modifier <<endl;



/**

constant: 0x7fff5fbff72c

const_p: 0x7fff5fbff72c

modifier: 0x7fff5fbff72c

**/

これは本当に奇妙なことですが、良いことです。C++ではconstであり、constであり、外界が大きく変化しても、私は変わらないということなのです。そうでなければ、本当に混乱し、constの存在意義がなくなってしまいます。

IBMのC++ガイド "*modifier = 7;"を呼び出すと、"Undefined Behavior"となります。未定義というのは、その文が標準C++で明示的に指定されておらず、それをどうするかはコンパイラに任されているということです。

ビット演算の左シフト操作も、論理的な左シフトか算術的な左シフトか分からないので、未定義の動作とみなすことができる。

また、次のような例もあります。v[i] = i++; という記述も、自己インクリメントを先に行うのか、配列内の位置を求めるのに使うのかが分からないので、未定義の動作となります。

未定義の動作に対してできることは、このようなステートメントを避けることです。これは特にconstデータに対して言えることで、constデータを決して再代入してはいけません。

const変数の値を変更したくないのなら、なぜconstにするのでしょうか?

なぜなら、ある関数を const でないパラメータで呼び出した場合、実際に渡したいパラメータは確かに const ですが、その関数がパラメータに変更を加えないことは分かっているからです。そこで、const_cast を使って const の修飾語を取り除き、関数がこの実際の引数を受け取れるようにする必要があります。


#include <iostream>
using namespace std;

void Printer (int* val,string seperator = "\n")
{
	cout << val<< seperator;
}

int main(void) 
{	
	const int consatant = 20;
	//Printer(consatant);//Error: invalid conversion from 'int' to 'int*'
	Printer(const_cast<int *>(&consatant));
	
	return 0;
}


この理由は、呼び出すメソッドが他の誰かによって書かれたものであることが考えられます。もうひとつの理由は、constオブジェクトがそれ自身のconstでないメソッドを呼び出したい場合です。constはクラス定義の中で関数のオーバーロードの目印として使われることもありますから。機会があれば、constの使用法について知っていることを復習してみようと思います。

IBMのC++ガイド には、もう1つコンストラクター解除が必要なケースがあります。

#include <iostream>
using namespace std;

int main(void) {
	int variable = 21;
	int* const_p = &variable;
	int* modifier = const_cast<int*>(const_p);
	
	*modifier = 7
	cout << "variable:" << variable << endl;
	
	return 0;
} 
/**
variable:7
**/

non-const 変数を定義しておきながら、const で修飾されたポインタで指し、ある時点で突然変更したくなったが、手元にはポインタしかなかったので、const に行って変更することができる。上のコードの結果からも、変更に成功したことが確認できる。

しかし、私はこれが良い設計だとは思いません。const_castを使ってconstの資格を取り除く目的は、間違いなくその内容を変更するためではなく、単に必要からだという原則に従うことが重要です。(私が言うように必然性があれば、const_castの有用性は低くなると思われますが、実際私もほとんど使いません)