最初は、この質問に興味をそそられました。それは本当に難しいことのように見え、テンプレート、依存関係、およびインクルードに関するすべてのコメントは理にかなっています。しかし、実際にこれを実装しようとすると、驚くほど簡単であることがわかりました。ですから、私がその質問を誤解したか、その質問には実際よりもはるかに見づらいという特別な性質があります。とにかく、これが私のコードです。
これは栄光のautoptr.hです:
#ifndef TESTPQ_AUTOPTR_H
#define TESTPQ_AUTOPTR_H
template<class T> class AutoPtr {
private:
T *p;
public:
AutoPtr() {p = new T();}
~AutoPtr() {delete p;}
T *operator->() {return p;}
};
#endif // TESTPQ_AUTOPTR_H
とてもシンプルに見えて、実際に機能するのだろうかと思ったので、テストケースを作成しました。これが私のbhです:
#ifndef TESTPQ_B_H
#define TESTPQ_B_H
class B {
public:
B();
~B();
void doSomething();
};
#endif // TESTPQ_B_H
そしてb.cpp:
#include <stdio.h>
#include "b.h"
B::B()
{
printf("B::B()\n");
}
B::~B()
{
printf("B::~B()\n");
}
void B::doSomething()
{
printf("B does something!\n");
}
次に、これを実際に使用するAクラスについて説明します。これがああです:
#ifndef TESTPQ_A_H
#define TESTPQ_A_H
#include "autoptr.h"
class B;
class A {
private:
AutoPtr<B> b;
public:
A();
~A();
void doB();
};
#endif // TESTPQ_A_H
そしてa.cpp:
#include <stdio.h>
#include "a.h"
#include "b.h"
A::A()
{
printf("A::A()\n");
}
A::~A()
{
printf("A::~A()\n");
}
void A::doB()
{
b->doSomething();
}
わかりました。最後に、Aを使用しますが、「bh」を含まないmain.cpp:
#include "a.h"
int main()
{
A a;
a.doB();
}
これで、実際には単一のエラーや警告なしでコンパイルされ、機能します。
d:\alqualos\pr\testpq>g++ -c -W -Wall b.cpp
d:\alqualos\pr\testpq>g++ -c -W -Wall a.cpp
d:\alqualos\pr\testpq>g++ -c -W -Wall main.cpp
d:\alqualos\pr\testpq>g++ -o a a.o b.o main.o
d:\alqualos\pr\testpq>a
B::B()
A::A()
B does something!
A::~A()
B::~B()
それはあなたの問題を解決しますか、それとも私はまったく違うことをしていますか?
編集1:それは標準かどうか?
さて、それは正しいことのようですが、今では他の興味深い質問につながります。以下のコメントでの議論の結果は次のとおりです。
上記の例ではどうなりますか?ahファイルはbhファイルを必要としません。これは、実際には何も行わずb
、宣言するだけであり、AutoPtrクラスのポインターは常に同じサイズであるため、そのサイズを認識しているためです。Bの定義を必要とするautoptr.hの唯一の部分はコンストラクタとデストラクタですが、これらはahで使用されないため、ahにbhを含める必要はありません。
しかし、なぜああ、Bのコンストラクターを使用しないのですか?Aのインスタンスを作成するたびに、Bのフィールドは初期化されませんか?その場合、コンパイラはAのインスタンス化のたびにこのコードをインライン化しようとしますが、失敗します。B::B()
上記の例では、呼び出しはa.cppユニットのコンパイル済みコンストラクターの先頭に置かれているように見えますA::A()
が、標準ではそれが必要ですか?
最初は、インスタントが作成されるたびにコンパイラがフィールド初期化コードをインライン化するのを妨げるものは何もないように思われるので、A a;
この擬似コードに変わります(もちろん実際のC ++ではありません)。
A a;
a.b->B();
a.A();
そのようなコンパイラは、標準に従って存在できますか?答えはノーです、彼らはできませんでした、そして標準はそれとは何の関係もありません。コンパイラが「main.cpp」ユニットをコンパイルするとき、A :: A()コンストラクタが何をするのかわかりません。の特別なコンストラクターを呼び出す可能性があるため、異なるコンストラクターで2回初期化さb
れる前に、デフォルトのコンストラクターをインライン化します。b
また、定義されている「a.cpp」ユニットA::A()
は個別にコンパイルされるため、コンパイラはそれをチェックする方法がありません。
さて、スマートコンパイラがBの定義を調べたい場合、デフォルトのコンストラクタ以外にコンストラクタがない場合はB::B()
、コンストラクタに呼び出しを行わずA::A()
、代わりにが呼び出されるたびにインライン化するとどうなるでしょうかA::A()
。コンパイラには、Bに現在他のコンストラクタがない場合でも、将来コンストラクタがないことを保証する方法がないため、これも発生しません。これをBクラス定義のbhに追加するとします。
B(int b);
次に、その定義をb.cppに入れ、それに応じてa.cppを変更します。
A::A():
b(17) // magic number
{
printf("A::A()\n");
}
これで、a.cppとb.cppを再コンパイルすると、main.cppを再コンパイルしなくても、期待どおりに機能します。これはバイナリ互換性と呼ばれ、コンパイラはそれを破るべきではありません。ただし、B::B()
呼び出しをインライン化すると、2つのB
コンストラクターを呼び出すmain.cppになります。ただし、コンストラクターと非仮想メソッドを追加してもバイナリ互換性が損なわれることはないため、適切なコンパイラーでそれを行うことは許可されません。
このようなコンパイラが存在しない最後の理由は、実際には意味がないためです。メンバーの初期化がインライン化されている場合でも、コードサイズが増えるだけで、メソッド呼び出しが1つあるため、パフォーマンスはまったく向上しA::A()
ません。このメソッドですべての作業を1か所で実行しないようにしましょう。
編集2:さて、Aのインラインおよび自動生成されたコンストラクターはどうですか?
A:A()
発生する別の質問は、ahとa.cppの両方から削除するとどうなるかということです。何が起こるかです:
d:\alqualos\pr\testpq>g++ -c -W -Wall a.cpp
d:\alqualos\pr\testpq>g++ -c -W -Wall main.cpp
In file included from a.h:4:0,
from main.cpp:1:
autoptr.h: In constructor 'AutoPtr<T>::AutoPtr() [with T = B]':
a.h:8:9: instantiated from here
autoptr.h:8:16: error: invalid use of incomplete type 'struct B'
a.h:6:7: error: forward declaration of 'struct B'
autoptr.h: In destructor 'AutoPtr<T>::~AutoPtr() [with T = B]':
a.h:8:9: instantiated from here
autoptr.h:9:17: warning: possible problem detected in invocation of delete
operator:
autoptr.h:9:17: warning: invalid use of incomplete type 'struct B'
a.h:6:7: warning: forward declaration of 'struct B'
autoptr.h:9:17: note: neither the destructor nor the class-specific operator
delete will be called, even if they are declared when the class is defined.
関連する唯一のエラーメッセージは、「不完全な型'structB'の無効な使用」です。基本的には、main.cppにbhを含める必要があることを意味しますが、なぜですか?自動生成されたコンストラクターは、インスタンス化するときにインライン化されるためですa
main.cppで。わかりましたが、これは常に発生する必要がありますか、それともコンパイラに依存しますか?答えは、コンパイラに依存することはできないということです。自動生成されたコンストラクターを非インラインにするコンパイラーはありません。その理由は、コードをどこに置くかがわからないためです。プログラマーの観点からは、答えは明らかです。コンストラクターは、クラスの他のすべてのメソッドが定義されているユニットに配置する必要がありますが、コンパイラーは、どのユニットがそれであるかを認識していません。さらに、クラスメソッドは複数のユニットに分散される可能性があり、場合によってはそれが理にかなっていることもあります(クラスの一部が何らかのツールによって自動生成される場合など)。
そしてもちろん、A::A()
inlineキーワードを使用するか、その定義をAクラス宣言内に配置することによって明示的にインライン化すると、同じコンパイルエラーが発生し、おそらく少しわかりにくいものになります。
結論
自動インスタンス化されたポインタに上記の手法を採用することはまったく問題ないようです。私が確信していない唯一のことは、AutoPtr<B> b;
ah内のものがどのコンパイラでも機能するということです。つまり、ポインターと参照を宣言するときに前方に削除されたクラスを使用できますが、テンプレートのインスタンス化パラメーターとして使用することは常に正しいですか?それは悪いことではないと思いますが、コンパイラーはそうではないと考えるかもしれません。グーグルもそれに関して有用な結果をもたらさなかった。