17

次のコードを検討してください。

#include <vector>

class A
{
public:    
    A(A&&);  // somewhat expensive

    static std::vector<A> make_As()
    {
        std::vector<A> result;
        result.push_back(A(3));
        result.push_back(A(4));
        return result;
    }

private:
    A(int);  // private constructor
};

の move コンストラクターは (何らかの理由で) やや高価であるため、呼び出しを避けて代わりにA使用したいと思います。emplace_back()

#include <vector>

class A
{
public:    
    A(A&&);  // somewhat expensive

    static std::vector<A> make_As()
    {
        std::vector<A> result;
        result.emplace_back(3);
        result.emplace_back(4);
        return result;
    }

private:
    A(int);  // private constructor
};

残念ながら、では、実際のコンストラクターの呼び出しは標準ライブラリ内の何かによって行われます。これは、のプライベート コンストラクターemplace_back()を呼び出すことができるほど十分な権限がありません。A

これについてできることはおそらくほとんどないと思いますが、呼び出しはemplace_back()のメンバー内で発生するAため、プライベート コンストラクターを呼び出すことができるはずだと感じています。

これに対する回避策はありますか?

私が考えることができる唯一のことは、にフレンド宣言を追加することですが、のフレンドAである必要がある正確なクラスA(つまり、実際にコンストラクターを呼び出そうとするクラス) は実装固有です (たとえば、 GCC__gnu_cxx::new_allocator<A>です)。EDIT : このようなフレンド宣言により、誰でもemplace_back() Aプライベート コンストラクターを使用してA's のコンテナーを作成できるようになるため、実際には何も解決しないことに気付きました。その時点でコンストラクターをパブリックにすることもできます...

UPDATE :A移動コンストラクターが高価であることだけが、それを呼び出さなくてもよい理由ではないことを追加する必要があります。Aまったく移動できない(コピーもできない)可能性があります。vectorもちろん、それは(emplace_back()ベクトルを再割り当てする必要があるかもしれないため) では機能しませんがdeque、同様の方法を持ちますが、何も再割り当てする必要がない では機能します。emplace_back()

4

3 に答える 3

20

1つの可能な回避策(またはkludge)は、ヘルパークラスを使用してパラメーターをプライベートctorに保持することですA(このクラスを呼び出しましょうEmplaceHelper)。EmplaceHelper にもプライベートな ctor が必要であり、 と相互に友好関係にある必要がありますAEmplaceHelperこれで必要なのは、 (おそらく const-ref 経由で)これを受け取る A の public ctor だけですemplace_back(EmplaceHelper(...))

EmplaceHelperによってのみ構築できるためA、パブリック ctor は実質的に非公開のままです。

std::tupleテンプレート化された EmplaceHelper (おそらく ctorパラメーターを保持するために 使用) を使用して、このアイデアを一般化することも可能です。

編集:実際には、GManNickG からの以下のコメントが私に単純なアイデアを与えてくれたので、これを複雑にしすぎたようです:private_ctor_t単なる空のクラスであるプライベートヘルパークラス (例では) を追加しますが、プライベートであるため、によってのみアクセスできますA。のコンストラクターを変更Aして、このプライベート クラスを最初 (または最後) のパラメーターとして含めます (そして公開します)。その結果A、プライベート コンストラクターがあるかのようにしか構築できませんでしたが、この構築は簡単に委譲できるようになりました。

このような:

#include <vector>
class A 
{ 
private:
    struct private_ctor_t {};

public:     
    A(private_ctor_t, int x) : A(x) // C++11 only, delegating constructor
    {}

    A(A&&) { /* somewhat expensive */ }

    static std::vector<A> make_As() 
    { 
        std::vector<A> result; 
        result.emplace_back(private_ctor_t{}, 3); 
        result.emplace_back(private_ctor_t{}, 4); 
        return result; 
    } 

private: 
    A(int) { /* private constructor */ }
}; 

委任されたコンストラクターが利用できない場合は、各バージョンの共通コードを除外するか、A(int)完全に削除して新しいバージョンのみを使用することができます。

于 2012-07-11T06:57:31.610 に答える
10

C ++ 11標準では、すべての標準コンテナがこのallocator::construct方法を使用してインプレース構築を行う必要があります。このように、あなたは単にstd::allocatorの友達を作ることができますA

技術的には、この関数は実際の構築呼び出しを他の何かに委任することが許可されていると思います。個人的には、どのオブジェクトがコンストラクターを呼び出すか、何を委任できるか、できないかを正確に強制することについて、仕様をもう少し厳密にする必要があると思います。

このような委任が発生した場合、または何らかの理由で、をstd::allocator除くすべての呼び出しを転送する独自のアロケータを提供できますconstruct。後者はお勧めしません。多くの標準的なコンテナの実装には、処理するための特別なコードがあり、std::allocatorそれによって占有するスペースが少なくて済みます。

そのようなフレンド宣言により、誰でもプライベートコンストラクターで構築されたAをAのコンテナーにemplace_back()できることに気付いたので、実際には何も解決されないので、その時点でコンストラクターを公開したほうがよいでしょう...

次に、あなたにとって何がより重要であるかを決定する必要があります:インプレース建設、またはプライベートを隠すこと。本質的に、インプレース構築とは、コードに含まれていない誰かが構築を行っていることを意味します。したがって、それを回避する方法はありません。一部の外部コードは、フレンドという名前にするか、コンストラクターをパブリックにする必要があります。要するに、コンストラクターは、建設を委任された人なら誰でも公にアクセス可能でなければなりません。

于 2012-07-11T06:52:36.450 に答える
2

少し単純化しましょう。V は A のプライベート コンストラクターにアクセスできないため、次のコードはコンパイルに失敗します。

struct V
{
    E(int i)
    {
        // ...
        auto a = A(i);
        // ...
    }
};

コードに戻ると、V は vector を単純化したものにすぎず、V::E は emplace_back の機能を単純化したものにすぎません。vector は A のプライベート コンストラクターにアクセスできず、 vector::emplace_back を呼び出す必要があります。

于 2012-07-11T05:56:55.403 に答える