基本クラスの抽象化とコピーの構築、経験則


9

多くの場合、オブジェクトのインターフェイスを分離するための抽象基本クラスを用意することをお勧めします。

問題は、C ++ではデフォルトでコピー構築であるIMHOがかなり壊れており、コピーコンストラクターがデフォルトで生成されていることです。

では、抽象基本クラスと派生クラスに生のポインタがある場合の落とし穴は何ですか?

class IAbstract
{
    ~IAbstract() = 0;
}

class Derived : public IAbstract
{
    char *theProblem;
    ...
}

IAbstract *a1 = new Derived();
IAbstract a2 = *a1;//???

そして、階層全体のコピー構築を完全に無効にしますか?コピー構造をプライベートとして宣言しますIAbstractか?

抽象基本クラスで3つのルールはありますか?


1
ポインタの代わりに参照を使用する:)
tp1

@ tp1:または少なくともいくつかのスマートポインター。
Benjamin Bannier

1
時には、既存のコードで作業しなければならないこともあります...すべてを瞬時に変更することはできません。
Coder

なぜデフォルトのコピーコンストラクタが壊れていると思いますか?
BЈовић

2
@Coder:Googleスタイルガイドは山積みであり、C ++の開発には絶対に負けます。
DeadMG 2012年

回答:


6

抽象クラスのコピー構築は、ほとんどの場合、割り当て演算子と同様にプライベートにする必要があります。

抽象クラスは、定義により、ポリモーフィック型になります。したがって、インスタンスが使用しているメモリの量がわからないため、安全にコピーまたは割り当てできません。実際には、スライスのリスクがあります:https : //stackoverflow.com/questions/274626/what-is-the-slicing-problem-in-c

C ++では、ポリモーフィック型を値で操作してはなりません。参照またはポインタ(または任意のスマートポインタ)で操作します。

これが、Javaがオブジェクトを参照のみで操作可能にした理由であり、C#とDがクラスと構造体を分離している理由です(最初の1つはポリモーフィックで参照型、2つ目は非ポリモーフィックで値型)。


2
Javaの物事はこれ以上良くありません。Javaでは、本当に、本当に必要な場合でも、何かをコピーするのは面倒であり、コピーするのを忘れるのは非常に簡単です。つまり、2つのデータ構造が同じ値クラス(例:日付)への参照を持ち、一方が日付を変更し、もう一方のデータ構造が壊れるという厄介なバグが発生します。
kevin cline 2012

3
ええ。それは問題ではありません-C ++がこの間違いをしないことを知っている人は誰でもいます。さらに重要なことは、自分が何をしているのかわかっている場合に、ポリモーフィック型を値で操作する理由はまったくありません。あなたは技術的にスリムな可能性に対して、ひざまずく反応を始めています。NULLポインターはより大きな問題です。
DeadMG

8

もちろん、それを保護して空にして、派生クラスが選択できるようにすることもできます。ただし、より一般的には、インスタンス化することが不可能であるため、コードは純粋に仮想関数を持っているため、とにかく禁止されIAbstractます。そのため、これは通常は問題ではありません。インターフェイスクラスをインスタンス化することはできないため、コピーすることはできません。派生クラスは、必要に応じてコピーを禁止または続行できます。


また、Abstract-> Derived1-> Derived2階層があり、Derived2がDerived1に割り当てられている場合はどうなりますか?言語のこれらの暗いコーナーはまだ私を驚かせます。
Coder

@コーダー:通常はPEBKACと見なされます。ただし、より直接的に、常にでプライベートoperator=(const Derived2&)を宣言できますDerived1
DeadMG

OK、多分これは本当に問題ではありません。クラスが虐待に対して安全であることを確認したいだけです。
Coder

2
そのためには、メダルを獲得する必要があります。
ワールドエンジニア、

2
@コーダー:誰による虐待?最善の方法は、正しいコードを簡単に記述できるようにすることです。C ++を知らないプログラマから身を守ろうとしても意味がありません。
ケビンcline

2

ctorと割り当てをプライベートにする(またはそれらをC ++ 11で= deleteと宣言する)ことで、コピーを無効にします。

ここでのポイントは、それを行う必要がある場所です。コードを維持するために、IAbstractは問題になりません。(あなたがしたことをすることに注意してください、あなたは*a1 IAbstractサブオブジェクトをa2に割り当て、への参照を失いますDerived。値の割り当ては多態的ではありません)

問題が付属していDerived::theproblemます。Derivedを別のDerivedにコピーすると、実際には、共有する*theproblemように設計されていない可能性のあるデータが共有される場合があります(delete theproblemデストラクタを呼び出す可能性のあるインスタンスが2つあります)。

その場合は、Derivedコピー不可で割り当て不可でなければなりません。もちろん、でコピーをプライベートにするIAbstractと、のデフォルトのコピーでDerived必要になるため、コピーDerivedもできなくなります。しかし、copy Derived::Derived(const Derived&)を呼び出さずに独自に定義した場合IAbtractでも、それらをコピーできます。

問題はDerivedにあり、ソリューションはDerivedにとどまる必要があります。それがポインターまたは参照によってのみアクセスされる動的のみのオブジェクトでなければならない場合、それはDerived自体でなければなりません。

class Derived
{
    ...
    Derived(const Derived&) = delete;
    Derived& operator=(const Derived&) = delete;
};

基本的に、Derivedクラスの設計者(Derivedがどのように機能し、どのようtheproblemに管理されるかを知っている必要があります)が、割り当てとコピーで何を行うかを決定します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.