なぜC ++ではコンストラクタのアドレスを取得できないのですか?


14

これが概念的に言語を破る特定の理由、またはこれが技術的に実行不可能である特定の理由がありますか?

使用法はnew演算子を使用します。

編集:私は「新しいオペレーター」と「新しいオペレーター」をまっすぐに取得することへの希望をあきらめ、率直になります。

問題のポイントは、なぜコンストラクターが特別なのかということです。もちろん、言語仕様は合法であるが必ずしも道徳的ではないことを教えてくれることに留意してください。通常、合法とは、言語の他の部分と論理的に一致するもの、単純で簡潔なもの、コンパイラーが実装できるものによって通知されます。これらの要因を評価する際の標準化委員会の考えられる理論的根拠は、意図的で興味深いものです。したがって、質問です。


コンストラクタのアドレスを取得することは問題になりませんが、型を渡すことはできます。テンプレートはそれを行うことができます。
陶酔14年

関数の引数として指定されるコンストラクターを使用してオブジェクトを構築する関数テンプレートがある場合はどうでしょうか?
プラクセオリック14年

1
私が考えることができる任意の例の代替案がありますが、それでも、なぜコンストラクターが特別なのですか?ほとんどのプログラミング言語では使用しない可能性が高いものがたくさんありますが、このような特別な場合には通常正当化が伴います。
プラキセオティック14年

1
@RobertHarveyファクトリクラスを入力しようとしていたときに、私に質問がありました。
プラキセオティック14年

1
C ++ 11はstd::make_uniquestd::make_sharedこの質問の根底にある実用的な動機を適切に解決できるかどうか疑問に思います。これらはテンプレートメソッドです。つまり、コンストラクターへの入力引数をキャプチャし、実際のコンストラクターに転送する必要があります。
rwong 16

回答:


10

メンバーへのポインター関数は、同じシグネチャを持つ複数のメンバー関数がある場合にのみ意味があります。そうでない場合、ポインターに可能な値は1つだけになります。しかし、C ++では同じクラスの異なるコンストラクターが異なるシグネチャを持たなければならないため、コンストラクターでは不可能です。

Stroustrupの代替案は、コンストラクターがクラス名と異なる名前を持つことができるC ++の構文を選択することでしたが、既存のctor構文の非常にエレガントな側面を妨げ、言語をより複雑にしました。私にとっては、オブジェクトの初期化をctorから別のinit関数(メンバーへのポインターができる通常のメンバー関数)に「アウトソーシング」することで簡単にシミュレートできる、めったに必要とされない機能を可能にするだけの高価格のように見えます作成した)。


2
それでも、なぜ防ぐのmemcpy(buffer, (&std::string)(int, char), size)ですか?(おそらく非常にコーシャではありませんが、これは結局C ++です。)
トーマスエディング14年

3
申し訳ありませんが、あなたが書いたことは意味がありません。コンストラクターを指すメンバーへのポインターがあっても何も問題はありません。また、ソースにリンクせずに何かを引用したように聞こえます。
BЈовић

1
@ThomasEding:この文は何をするのでしょうか?文字列ctor somewgereのアセンブリコードをコピーしますか?「サイズ」はどのように決定されますか(標準のメンバー関数に相当するものを試しても)。
Doc Brown 14年

空き関数ポインタのアドレスを指定した場合と同じことを期待していますmemcpy(buffer, strlen, size)。おそらく、アセンブリをコピーしますが、誰が知っているでしょう。クラッシュせずにコードを呼び出すことができるかどうかは、使用するコンパイラに関する知識が必要です。サイズの決定についても同様です。プラットフォームに大きく依存しますが、多くの移植性のないC ++コンストラクトが製品コードで使用されます。私はそれを禁止する理由は見当たりません。
トーマスエディング14年

@ThomasEding:適合C ++コンパイラーは、関数ポインターにデータポインターであるかのようにアクセスしようとすると、診断を行うことが期待されています。準拠していないC ++コンパイラは何でもできますが、コンストラクタにアクセスするための非c ++の方法も提供できます。これは、適合コードで使用できない機能をC ++に追加する理由ではありません。
バートヴァンインゲンシェナウ14年

5

コンストラクターは、オブジェクトがまだ存在しないときに呼び出す関数であるため、メンバー関数にはなれません。静的である可能性があります。

実際、コンストラクターは、メモリーが割り当てられた後、完全に初期化される前にthisポインターで呼び出されます。結果として、コンストラクターには多くの特権機能があります。

コンストラクターへのポインターがある場合、それは静的ポインター、ファクトリー関数のようなもの、またはメモリー割り当て直後に呼び出されるものへの特別なポインターのいずれかでなければなりません。通常のメンバー関数ではなく、コンストラクターとして機能します。

頭に浮かぶ唯一の有用な目的は、使用するコンストラクターで間接的にできるようにnew演算子に渡すことができる特別な種類のポインターです。これは便利かもしれませんが、重要な新しい構文が必要になります。おそらく答えは次のとおりです。

一般的な初期化コードをリファクタリングしたい場合、通常は通常のメモリ関数で十分な答えが得られ、それらの1つへのポインタを取得できます。


これが最も正しい答えのようです。私は何年も前のオペレーターnewと「新しいオペレーター」の内部動作に関する記事を思い出します。operator new()はスペースを割り当てます。newオペレーターは、割り当てられたスペースでコンストラクターを呼び出します。コンストラクタの呼び出しにはスペースが必要なため、コンストラクタのアドレスを取得することは「特別」です。このようなコンストラクターを呼び出すためのアクセスは、新しい配置です。
ビル・ドアの

1
「存在する」という言葉は、オブジェクトがアドレスを持ち、メモリを割り当てたが初期化されていないという詳細を曖昧にします。メンバー関数かどうかにかかわらず、thisポインターを取得すると、関数がオブジェクトインスタンスに明確に関連付けられているため(初期化されていない場合でも)、関数がメンバー関数になると思います。そうは言っても、答えは良い点を提起します:コンストラクターは、初期化されていないオブジェクトで呼び出すことができる唯一のメンバー関数です。
プラクセオリック14年

気にせず、明らかに「特別なメンバー機能」の指定がある。C ++ 11標準の条項12:「デフォルトのコンストラクター(12.1)、コンストラクターのコピーと代入演算子のコピー(12.8)、コンストラクターの移動と代入演算子の移動(12.8)、およびデストラクター(12.4)は特別なメンバー関数です。」
プラクセオリック14年

12.1:「コンストラクタは仮想(10.3)または静的(9.4)であってはならない」。(私の強調)
プラキソリック14年

1
実際、デバッグシンボルを使用してコンパイルし、スタックトレースを検索すると、実際にはコンストラクターへのポインターがあります。私ができなかったのは、このポインターを取得する構文を見つけることです(&A::A試したどのコンパイラーでも動作しません。)
alfC

-3

これは、それらがコンストラクタの戻り値型ではなく、コンストラクタがメモリ内にスペースを予約していないためです。宣言中の変数の場合に行うようにu。たとえば、単純な変数Xを書き込むと、コンパイラはこの意味を理解できないため、コンパイラはエラーを生成します。しかし、Int xを書くとき; その後、コンパイラーはint型data variableであることを知るようになり、変数用にスペースを予約します。

結論:-結論は、戻り値の型を除外するため、メモリ内のアドレスを取得しないということです。


1
コンストラクタ内のコードは、どこかにある必要があるため、メモリ内にアドレスを持っている必要があります。スタック上のスペースを確保する必要はありませんが、メモリのどこかにある必要があります。あなたは可能な値を返さない関数のアドレスを取ります。(void)(*fptr)()戻り値のない関数へのポインタを宣言します。
プラキソライト

2
あなたは質問のポイントを見逃しました-元の投稿は、コンストラクターが提供した結果ではなく、コンストラクターのコードのアドレスを取得することについて尋ねました。さらに、このボードでは、「u」は「you」の代わりに使用することはできません。
BobDalgleish

praxeolitic氏、戻り値の型に言及しない場合、コンパイラはctorに特定のメモリ位置を設定せず、その位置は内部的に設定されます。...コンパイラ?間違っている場合は、正しい答えで私を修正してください
ロヴィッシュゴヤル

また、参照変数についても教えてください。参照変数のアドレスを取得できますか?いいえの場合、printf( "%u"、&(&(j))); &j = xでx = 10の場合に印刷しますか?printfで印刷されたアドレスとxのアドレスが同じではないため
ロヴィッシュゴヤル

-4

私はワイルドな推測をします:

C ++のコンストラクタとデストラクタは、関数ではありません。マクロです。オブジェクトが作成されるスコープと、オブジェクトが破棄されるスコープにインライン化されます。同様に、コンストラクタもデストラクタもありません。オブジェクトはISです。

実際、クラス内の他の関数も関数ではなく、それらのアドレスを取得するためにDONTがインライン化されるインライン関数であると思いますその関数を最適化します)、そして、関数は「まだそこにある」ように見えます。

C ++の「オブジェクト」の仮想テーブルはJavaScriptオブジェクトのようなものではなく、そのコンストラクタを取得し、実行時にオブジェクトを作成することができnew XMLHttpRequest.constructorます。 、オブジェクトを作成する機能を除きます。そして、それは構造体を削除しようとするようなものであるため、オブジェクトを「削除」することすら意味がありません。クラスを4つの整数として使用します。

/* i imagine this string gets compiled into a struct, one of which's members happens to be a const char * which is initialized to exactly your string: no function calls are made during construction. */
std::string a = "hello, world";
int *myInt = (int *)(*((void **)&a));
myInt[0] = 3;
myInt[1] = 9;
myInt[2] = 20;
myInt[3] = 300;

メモリリークはありません。問題はありません。ただし、オブジェクトインターフェイスと文字列のために予約されているスタックスペースを事実上無駄にしていますが、プログラムを破壊することはありません(使用しない限り)再び文字列として)。

実際、以前の仮定が正しい場合:文字列の完全なコストは、これらの32バイトと定数文字列スペースを格納するコストです:関数はコンパイル時にのみ使用され、インライン化されて捨てられる可能性がありますオブジェクトが作成され、使用されます(構造体で作業しており、関数呼び出しなしで直接参照している場合と同様に、関数ジャンプの代わりに呼び出しが重複していることを確認しますが、これは通常より速く、スペースを節約します)。本質的に、関数を呼び出すたびに、コンパイラーはその呼び出しを、言語設計者が設定した例外を除き、文字通り実行するための命令に置き換えるだけです。

要約:C ++オブジェクトには、それらが何であるかがわかりません。それらとインターフェースするためのすべてのツールは静的にインライン化され、実行時に失われます。これにより、構造体にデータを入力するのと同じくらい効率的にクラスを操作でき、関数をまったく呼び出さずにそのデータを直接操作できます(これらの関数はインライン化されます)。

これは、コンパイラがこの情報を捨てることができないため、実行時のオーバーヘッド、メモリ管理、構築の呼び出しを犠牲にして、型情報を動的に保持するCOM / ObjectiveCおよびjavascriptのアプローチとはまったく異なります。動的ディスパッチ用。これにより、実行時にプログラムと「対話」し、実行中に反映可能なコンポーネントを使用してプログラムを開発することができます。


2
申し訳ありませんが、この「回答」の一部は間違っているか、誤解を招く恐れがあります。残念なことに、コメントスペースはすべてをリストするには小さすぎます(ほとんどのメソッドはインライン化されません。これにより、仮想ディスパッチが妨げられ、バイナリが膨張します。インライン化された場合でも、最悪の場合はスタックが破損し、最良の場合は仮定に適合しません; ...)
hoffmale

答えは素朴で、コンストラクタ/デストラクタを参照できない理由について推測したいだけです。仮想クラスの場合、vtableが持続し、vtableが参照できるようにアドレス可能なコードがメモリ内になければならないことに同意します。ただし、仮想クラスを実装しないクラスは、std :: stringの場合のようにインライン化されているようです。すべてがインライン化されるわけではありませんが、メモリ内のどこかに「匿名」コードブロックに最小限に入れられていないように見えるもの。また、コードはどのようにスタックを破損しますか?確かに文字列を失いましたが、それ以外は再解釈するだけです。
ドミトリー

メモリ位置の内容が意図せずに変更されると、コンピュータプログラムでメモリ破損が発生します。このプログラムは意図的にそれを行い、その文字列をもう使用しようとしないため、破損はなく、スタックスペースを無駄にするだけです。しかし、はい、stringの不変式はもはや維持されず、スコープを乱雑にします(最後にスタックが回復されます)。
ドミトリー

文字列の実装によっては、不要なバイトを上書きする場合があります。文字列がstruct { int size; const char * data; };(あなたが推測するように)何かのような場合、x86マシンで8バイトのみを予約したメモリアドレスに4 * 4バイト= 16バイトを書き込むため、8バイトが他のデータに上書きされます(スタックが破損する可能性があります) )。幸いなことに、std::string通常は短い文字列に対してインプレース最適化が行われるため、主要なstd実装を使用する場合の例には十分な大きさにする必要があります。
hoffmale 16

@hoffmaleあなたは絶対に正しいです、それは4バイトかもしれません、それは8バイトかもしれませんし、1バイトかもしれません。ただし、文字列のサイズがわかれば、そのメモリが現在のスコープ内でスタック上にあることもわかるので、好きなように使用できます。私のポイントは、構造を知っていれば、クラスをIUnknownのvtableの一部として識別するuuidを持つCOMオブジェクトとは異なり、クラスに関する情報に依存しない方法でパックされるということです。コンパイラは、インライン化またはマングルされた静的関数を介してこのデータに直接アクセスします。
ドミトリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.