サーチ…
仮想デストラクタと保護デストラクタ
継承されるように設計されたクラスは、Baseクラスと呼ばれます。そのようなクラスの特別なメンバー機能に注意する必要があります。
実行時に多態的に(基本クラスへのポインタを介して)使用されるように設計されたクラスは、デストラクタをvirtual
宣言する必要がありvirtual
。これにより、たとえオブジェクトが基本クラスへのポインタによって破壊されたとしても、オブジェクトの派生部分を適切に破棄することができます。
class Base {
public:
virtual ~Base() = default;
private:
// data members etc.
};
class Derived : public Base { // models Is-A relationship
public:
// some methods
private:
// more data members
};
// virtual destructor in Base ensures that derived destructors
// are also called when the object is destroyed
std::unique_ptr<Base> base = std::make_unique<Derived>();
base = nullptr; // safe, doesn't leak Derived's members
クラスがポリモーフィックである必要はなく、そのインタフェースを継承させる必要がある場合は、非仮想protected
デストラクタを使用します。
class NonPolymorphicBase {
public:
// some methods
protected:
~NonPolymorphicBase() = default; // note: non-virtual
private:
// etc.
};
そのようなクラスは、スライスによる静かなリークを避けるために、ポインタを通して決して破棄することはできません。
この手法は、 private
ベースクラスとして設計されたクラスに特に適用されます。そのようなクラスは、いくつかの一般的な実装の詳細をカプセル化し、 virtual
メソッドをカスタマイズポイントとして提供するために使用されます。この種のクラスはポリモーフィックに使用されるべきではなく、 protected
デストラクタはこの要件をコードに直接記述するのに役立ちます。
最後に、いくつかのクラスは、それらが基本クラスとして決して使用されないことを要求することがあります。この場合、クラスはfinal
とマークすることができます。この場合、通常の非仮想パブリックデストラクタは問題ありません。
class FinalClass final { // marked final here
public:
~FinalClass() = default;
private:
// etc.
};
暗黙的な移動とコピー
デストラクタを宣言すると、コンパイラが暗黙のムーブコンストラクタを生成して代入演算子を移動することができなくなることに注意してください。デストラクタを宣言する場合は、移動操作に適切な定義を追加することも忘れないでください。
さらに、移動操作を宣言するとコピー操作の生成が抑制されるため、これらのオブジェクトも追加する必要があります(このクラスのオブジェクトにコピーセマンティクスが必要な場合)。
class Movable {
public:
virtual ~Movable() noexcept = default;
// compiler won't generate these unless we tell it to
// because we declared a destructor
Movable(Movable&&) noexcept = default;
Movable& operator=(Movable&&) noexcept = default;
// declaring move operations will suppress generation
// of copy operations unless we explicitly re-enable them
Movable(const Movable&) = default;
Movable& operator=(const Movable&) = default;
};
コピーとスワップ
リソースを管理するクラスを作成する場合は、すべての特別なメンバー関数を実装する必要があります( 3/5 /ゼロのルールを参照)。コピーコンストラクタと代入演算子を書くための最も直接的なアプローチは次のとおりです。
person(const person &other)
: name(new char[std::strlen(other.name) + 1])
, age(other.age)
{
std::strcpy(name, other.name);
}
person& operator=(person const& rhs) {
if (this != &other) {
delete [] name;
name = new char[std::strlen(other.name) + 1];
std::strcpy(name, other.name);
age = other.age;
}
return *this;
}
しかし、このアプローチにはいくつかの問題があります。それは、強力な例外保証を失敗した-場合はnew[]
投げ、我々はすでにが所有するリソースをクリアしましたthis
して回復することはできません。私たちは、コピーの割り当てにおいて多くのコピー構成のロジックを複製しています。そして、私たちは、通常、コピー操作にオーバーヘッドを追加する自己割り当てチェックを覚えておく必要がありますが、依然として重要です。
強力な例外保証を満たし、コードの重複を避けるために(後続の移動代入演算子で倍にする)、コピー・アンド・スワップ・イディオムを使用できます。
class person {
char* name;
int age;
public:
/* all the other functions ... */
friend void swap(person& lhs, person& rhs) {
using std::swap; // enable ADL
swap(lhs.name, rhs.name);
swap(lhs.age, rhs.age);
}
person& operator=(person rhs) {
swap(*this, rhs);
return *this;
}
};
なぜこれは機能しますか?私たちがいるとき何が起こるかを考えて
person p1 = ...;
person p2 = ...;
p1 = p2;
まず、 p2
からrhs
をコピーします(ここでは複製する必要はありません)。その操作がスローされた場合、 operator=
とp1
は何もしません。次に、 *this
とrhs
間でメンバーを入れ替えると、 rhs
は範囲外になります。ときoperator=
、それは暗黙のうちに、元のリソースクリーンthis
(我々が複製する必要はありませんでしたデストラクタを介して)。自己割り当ても機能します。コピーアンドスワップ(割り当てと解放の追加が必要です)ではあまり効率的ではありませんが、それが起こりそうなシナリオでは、典型的なユースケースを考慮して減速しません。
上記の定式化は、すでに移動割り当てのために既に行われています。
p1 = std::move(p2);
ここでは、 rhs
をp2
から移動して構築します。残りはすべて有効です。クラスが移動可能であるがコピーできない場合、この代入演算子は削除されたコピーコンストラクタのために単純に不正な形になるため、コピー代入を削除する必要はありません。
デフォルトコンストラクタ
デフォルトのコンストラクタは、呼び出されたときにパラメータを必要としないコンストラクタの一種です。それは、それが構築する型の後に命名され、それのメンバ関数です(すべてのコンストラクタがそうです)。
class C{
int i;
public:
// the default constructor definition
C()
: i(0){ // member initializer list -- initialize i to 0
// constructor function body -- can do more complex things here
}
};
C c1; // calls default constructor of C to create object c1
C c2 = C(); // calls default constructor explicitly
C c3(); // ERROR: this intuitive version is not possible due to "most vexing parse"
C c4{}; // but in C++11 {} CAN be used in a similar way
C c5[2]; // calls default constructor for both array elements
C* c6 = new C[2]; // calls default constructor for both array elements
「パラメータなし」要件を満たす別の方法は、開発者がすべてのパラメータにデフォルト値を提供することです。
class D{
int i;
int j;
public:
// also a default constructor (can be called with no parameters)
D( int i = 0, int j = 42 )
: i(i), j(j){
}
};
D d; // calls constructor of D with the provided default values for the parameters
状況によっては(つまり、開発者がコンストラクタを提供せず、他の不適格条件もない場合)、コンパイラは暗黙的に空のデフォルトコンストラクタを提供します。
class C{
std::string s; // note: members need to be default constructible themselves
};
C c1; // will succeed -- C has an implicitly defined default constructor
他のタイプのコンストラクタを使用することは、先に述べた不適格条件の1つです。
class C{
int i;
public:
C( int i ) : i(i){}
};
C c1; // Compile ERROR: C has no (implicitly defined) default constructor
暗黙的なデフォルトのコンストラクタの作成を防ぐため、一般的な手法は、それをprivate
(宣言なし)として宣言することです。誰かがコンストラクタを使用しようとすると、コンパイルエラーが発生することになります(これは、コンパイラによってプライベートエラーまたはリンカエラーになる)。
デフォルトのコンストラクタ(暗黙的に似た関数)が定義されていることを確認するために、開発者は空のものを明示的に書くことができます。
C ++ 11では、開発者はdelete
キーワードを使用して、コンパイラがデフォルトのコンストラクタを提供しないようにdelete
こともできます。
class C{
int i;
public:
// default constructor is explicitly deleted
C() = delete;
};
C c1; // Compile ERROR: C has its default constructor deleted
さらに、開発者は、コンパイラにデフォルトのコンストラクタを提供することを明示することもできます。
class C{
int i;
public:
// does have automatically generated default constructor (same as implicit one)
C() = default;
C( int i ) : i(i){}
};
C c1; // default constructed
C c2( 1 ); // constructed with the int taking constructor
<type_traits>
std::is_default_constructible
を使用して、型にデフォルトのコンストラクタがあるかどうか(またはプリミティブ型かどうか)を判断できます。
class C1{ };
class C2{ public: C2(){} };
class C3{ public: C3(int){} };
using std::cout; using std::boolalpha; using std::endl;
using std::is_default_constructible;
cout << boolalpha << is_default_constructible<int>() << endl; // prints true
cout << boolalpha << is_default_constructible<C1>() << endl; // prints true
cout << boolalpha << is_default_constructible<C2>() << endl; // prints true
cout << boolalpha << is_default_constructible<C3>() << endl; // prints false
C ++ 11では、 std::is_default_constructible
以外のバージョンのstd::is_default_constructible
を使用することは可能std::is_default_constructible
。
cout << boolalpha << is_default_constructible<C1>::value << endl; // prints true
デストラクタ
デストラクタは、ユーザ定義オブジェクトが破棄されるときに呼び出される引数のない関数です。それは~
接頭辞で破壊する型の名前に由来します。
class C{
int* is;
string s;
public:
C()
: is( new int[10] ){
}
~C(){ // destructor definition
delete[] is;
}
};
class C_child : public C{
string s_ch;
public:
C_child(){}
~C_child(){} // child destructor
};
void f(){
C c1; // calls default constructor
C c2[2]; // calls default constructor for both elements
C* c3 = new C[2]; // calls default constructor for both array elements
C_child c_ch; // when destructed calls destructor of s_ch and of C base (and in turn s)
delete[] c3; // calls destructors on c3[0] and c3[1]
} // automatic variables are destroyed here -- i.e. c1, c2 and c_ch
ほとんどの場合(つまり、ユーザがデストラクタを提供せず、その他の不適格条件がない場合)、コンパイラはデフォルトのデストラクタを暗黙的に提供します。
class C{
int i;
string s;
};
void f(){
C* c1 = new C;
delete c1; // C has a destructor
}
class C{
int m;
private:
~C(){} // not public destructor!
};
class C_container{
C c;
};
void f(){
C_container* c_cont = new C_container;
delete c_cont; // Compile ERROR: C has no accessible destructor
}
C ++ 11では、開発者は、コンパイラがデフォルトのデストラクタを提供できないようにすることで、この動作を無効にできます。
class C{
int m;
public:
~C() = delete; // does NOT have implicit destructor
};
void f{
C c1;
} // Compile ERROR: C has no destructor
さらに、開発者は、コンパイラにデフォルトのデストラクタを提供することを明示することもできます。
class C{
int m;
public:
~C() = default; // saying explicitly it does have implicit/empty destructor
};
void f(){
C c1;
} // C has a destructor -- c1 properly destroyed
<type_traits>
std::is_destructible
を使用して、型にデストラクタがあるかどうか(またはプリミティブ型かどうか)を判断できます。
class C1{ };
class C2{ public: ~C2() = delete };
class C3 : public C2{ };
using std::cout; using std::boolalpha; using std::endl;
using std::is_destructible;
cout << boolalpha << is_destructible<int>() << endl; // prints true
cout << boolalpha << is_destructible<C1>() << endl; // prints true
cout << boolalpha << is_destructible<C2>() << endl; // prints false
cout << boolalpha << is_destructible<C3>() << endl; // prints false