29

私は自分の構造体を更新していて、それにstd::stringメンバーを追加したいと思っていました。元の構造体は次のようになります。

struct Value {
  uint64_t lastUpdated;

  union {
    uint64_t ui;
    int64_t i;
    float f;
    bool b;
  };
};

もちろん、std :: stringメンバーをユニオンに追加するだけで、コンパイルエラーが発生します。これは、通常、オブジェクトの重要なコンストラクターを追加する必要があるためです。 std :: stringの場合(informit.comからのテキスト)

std :: stringは6つの特殊メンバー関数すべてを定義するため、Uには、暗黙的に削除されたデフォルトコンストラクター、コピーコンストラクター、コピー割り当て演算子、ムーブコンストラクター、ムーブ代入演算子、およびデストラクタがあります。事実上、これは、特別なメンバー関数の一部またはすべてを明示的に定義しない限り、Uのインスタンスを作成できないことを意味します。

次に、Webサイトは次のサンプルコードを提供します。

union U
{
int a;
int b;
string s;
U();
~U();
};

ただし、構造体内で匿名の共用体を使用しています。freenodeで##C++を尋ねたところ、その正しい方法は、代わりにコンストラクターを構造体に配置することであり、次のサンプルコードを提供してくれました。

#include <new>

struct Point  {
    Point() {}
    Point(int x, int y): x_(x), y_(y) {}
    int x_, y_;
};

struct Foo
{
  Foo() { new(&p) Point(); }
  union {
    int z;
    double w;
    Point p;
  };
};

int main(void)
{
}

しかし、そこから、std :: stringが定義する必要のある残りの特殊関数を作成する方法を理解できません。さらに、その例のctorがどのように機能しているかについて完全に明確ではありません。

誰かにこれをもう少し明確に説明してもらえますか?

4

2 に答える 2

23

ここに新しい配置をする必要はありません。

バリアントメンバーは、コンパイラによって生成されたコンストラクタによって初期化されませんが、1つを選択し、通常のctor-initializer-listを使用して初期化するのに問題はありません。匿名ユニオン内で宣言されたメンバーは、実際には包含クラスのメンバーであり、包含クラスのコンストラクターで初期化できます。

この動作については、セクション9.5で説明しています。[class.union]

ユニオンのようなクラスは、ユニオンまたは直接メンバーとして匿名のユニオンを持つクラスです。ユニオンのようなクラスには、バリアントメンバーXのセットがあります。ユニオンの場合、そのバリアントメンバーは非静的データメンバーです。それ以外の場合、そのバリアントメンバーは、のメンバーであるすべての匿名ユニオンの非静的データメンバーです。XX

およびセクション12.6.2 [class.base.init]

ctor -initializerは、コンストラクターのクラスのバリアントメンバーを初期化する場合があります。ctor-initializerが同じメンバーまたは同じ基本クラスに複数のmem-initializerを指定している場合、 ctor-initializerの形式は正しくありません。

したがって、コードは次のようになります。

#include <new>

struct Point  {
    Point() {}
    Point(int x, int y): x_(x), y_(y) {}
    int x_, y_;
};

struct Foo
{
  Foo() : p() {} // usual everyday initialization in the ctor-initializer
  union {
    int z;
    double w;
    Point p;
  };
};

int main(void)
{
}

もちろん、コンストラクターで初期化された他のメンバー以外のバリアントメンバーをアクティブ化する場合は、配置newを引き続き使用する必要があります。

于 2012-05-22T02:07:51.833 に答える
14

このnew (&p) Point()例は、(配置の新しい式を介した)標準配置new演算子の呼び出しであるため、を含める必要があります<new>。その特定の演算子は、メモリを割り当て、渡されたもの(この場合は&pパラメータ)のみを返すという点で特別です。式の最終的な結果は、オブジェクトが作成されたことです。

この構文を明示的なデストラクタ呼び出しと組み合わせると、オブジェクトの存続期間を完全に制御できます。

// Let's assume storage_type is a type
// that is appropriate for our purposes
storage_type storage;

std::string* p = new (&storage) std::string;
// p now points to an std::string that resides in our storage
// it was default constructed

// *p can now be used like any other string
*p = "foo";

// Needed to get around a quirk of the language
using string_type = std::string;

// We now explicitly destroy it:
p->~string_type();
// Not possible:
// p->~std::string();

// This did nothing to our storage however
// We can even reuse it
p = new (&storage) std::string("foo");

// Let's not forget to destroy our newest object
p->~string_type();

std::stringクラス内のメンバー(これを呼びましょうs)をいつどこで構築および破棄するValueかは、の使用パターンによって異なりますs。この最小限の例では、特別なメンバーでそれを構築する(したがって破壊する)ことはありません。

struct Value {
    Value() {}

    Value(Value const&) = delete;
    Value& operator=(Value const&) = delete;

    Value(Value&&) = delete;
    Value& operator=(Value&&) = delete;

    ~Value() {}

    uint64_t lastUpdated;

    union {
        uint64_t ui;
        int64_t i;
        float f;
        bool b;
        std::string s;
    };
};

したがって、以下は次の有効な使用法ですValue

Value v;
new (&v.s) std::string("foo");
something_taking_a_string(v.s);
using string_type = std::string;
v.s.~string_type();

お気づきかもしれませんが、コピーと移動を無効にしましValueた。その理由は、アクティブなメンバーが存在する場合はそれがどれであるかを知らずに、ユニオンの適切なアクティブなメンバーをコピーまたは移動できないためです。

于 2012-05-22T01:48:44.820 に答える