2013-05-19 8 views
18

私は、別のオブジェクトの内部にstd::functionとして格納しようとしている、テンプレートのないファンクタオブジェクトを持っています。このオブジェクトは本当にヘビーなので、コピー不可能とマークされていますが、移動コンストラクタはあります。しかし、一時的なコンストラクタからstd :: functionを構築しようとしたり、それを代入しようとすると失敗します。std :: functionをrvalue参照から一時的なファンクタオブジェクトに移動構築することはできますか?

ここでは、エラーを引き起こす最小限の例を示します。さらに簡単な工事、私はローカル関数を作ってるんだところ、私もエラーを取得するには

// pretend this is a really heavyweight functor that can't be copied. 
struct ExampleTest 
{ 
    int x; 
    int operator()(void) const {return x*2;} 
    ExampleTest() :x(0){} 
    ExampleTest(int a) :x(a){} 

    // allow move 
    ExampleTest(ExampleTest &&other) :x(other.x) {}; 

private: // disallow copy, assignment 
    ExampleTest(const ExampleTest &other); 
    void operator=(const ExampleTest &other); 
}; 

// this sometimes stores really big functors and other times stores tiny lambdas. 
struct ExampleContainer 
{ 
    ExampleContainer(int); 
    std::function<int(void)> funct; 
}; 

/******** ERROR: 
Compiler error: 'ExampleTest::ExampleTest' : cannot access private member 
declared in class 'ExampleTest' 
******************/ 
ExampleContainer::ExampleContainer(int x) 
    : funct(ExampleTest(x)) 
{} 

/******** ERROR: 
Compiler error: 'ExampleTest::ExampleTest' : cannot access private member 
declared in class 'ExampleTest' 
******************/ 
int SetExample(ExampleContainer *container) 
{ 
    container->funct = ExampleTest(); 
    return container->funct(); 
} 

int ContrivedExample() 
{ 
    // extra parens to sidestep most vexing parse 
    std::function<int()> zug((ExampleTest())); 
    /*** ERROR: 'ExampleTest::ExampleTest' : cannot access private member 
     declared in class 'ExampleTest' */ 
    int troz = zug() ; 
    return troz; 
} 

これまでのところ、私が言うことができるように、これらすべての場合で、一時的なExampleTestは、右辺値として関数コンストラクタに渡されるべきです。しかし、コンパイラはそれらをコピーしたい。

何がありますか?コピーできない(しかしコピー可能な)ファンクタオブジェクトをstd :: functionコンストラクタに渡すことは可能ですか?ポインタなどの回避策がありますが、ここで何が起こっているのか理解したいと思います。

上記の特定のエラーは、CTP C++ 11パッチを適用したVisual Studio 2012のものです。 GCC 4.8とClang 3も落ち込み、独自のエラーメッセージが表示されます。

+0

C++ 11を使用している場合は、private copy-ctorおよびassignment-opの代わりに中括弧初期化 'ExampleTest {}'と '= delete'を使用してください。 – dyp

+0

移譲代入演算子を追加するとどうなりますか? –

+0

@DyP初期化子リストの構築や '= delete'構文を確実にサポートするために私がアクセスできるコンパイラはありません。 – Crashworks

答えて

18

This object is really heavyweight, so it's marked as uncopyable, but it does have a move constructor.

ファンクタが非コピー可能であるならば、それはstd::functionで使用されているために必要な要件を満たしていません。段落C++ 11標準指定の20.8.11.2.1/7:

template<class F> function(F f); 
template <class F, class A> function(allocator_arg_t, const A& a, F f); 

7 Requires: F shall be CopyConstructible. f shall be Callable (20.8.11.2) for argument types ArgTypes and return type R . The copy constructor and destructor of A shall not throw exceptions.

+2

私は以前これに気付きましたが、標準では「すべてのコールラッパー(20.8.1)はMoveConstructibleになります」、「function」にはrvalue-taking moveコンストラクタがあります。それは矛盾しているようです。 – Crashworks

+0

@Crashworks: 'std :: function'オブジェクト自体はmove-constructibleですが、あなたが作成するはずのファンクタはコピーコンストラクタブルでなければなりません –

+9

標準はコピー構築私は本当にそうする必要はないと思う!たぶん欠陥報告の価値があるかもしれません。私は既存のレポートをすばやく見ていましたが、私はこの問題について言及していませんでした。私は、TR1クラスがC++ 2011にインポートされたときの見落としであると思うでしょう。 –

2

のstd ::関数はファンクタオブジェクトの右辺値から移動に構成することができます。そして、ほとんどの実装がそうしています。

std :: functionの "my target is copy-constructable"の要件は、コピーを構成可能にするという独自の要件によるものです。 std :: functionの型はターゲットのシグネチャ(例えば:void(int))によってのみ定義され、std :: function自体は標準によってコピーコンストラクタブルに定義されます。したがって、std ::関数をコピーして構築するときは、そのターゲット(基底のファンクタ)のcopy-ctorを呼び出す必要があります。だから、それは1つのターゲットを必要とします。他の選択肢はありません。

ターゲットがコピーコンストラクタブルである必要があるため、標準では、rvalue呼び出し可能オブジェクトからstd ::を構築するときに、実装ではなく、コピーする必要があります。実装はおそらく、あなたの呼び出し可能なオブジェクトのmove-ctorを呼び出すだけです。 GCC、例えば

(MSVCが類似している)STDのCTORの実装::任意の呼び出し可能オブジェクトからの関数:

例および試験と

より詳細な付加情報

template<typename _Res, typename... _ArgTypes> 
    template<typename _Functor, typename> 
    function<_Res(_ArgTypes...)>:: 
    function(_Functor __f) 
    : _Function_base() 
    { 
     typedef _Function_handler<_Signature_type, _Functor> _My_handler; 

     // don't need to care about details below, but when it uses __f, it 
     // either uses std::move, or passes it by references 
     if (_My_handler::_M_not_empty_function(__f)) 
     { 
      _My_handler::_M_init_functor(_M_functor, std::move(__f)); 
      _M_invoker = &_My_handler::_M_invoke; 
      _M_manager = &_My_handler::_M_manager; 
     } 
    } 

"_Functor __f"の引数の値を渡すと、移動コンストラクタがある場合はそのコンストラクタが使用され、移動コンストラクタがない場合はそのコンストラクタが使用されます。次のテストプログラムを発揮できるよう:最後に

int main(){ 
    using namespace std; 
    struct TFunctor 
    { 
     TFunctor() = default; 
     TFunctor(const TFunctor&) { cout << "cp ctor called" << endl; } 
     TFunctor(TFunctor&&) { cout << "mv ctor called" << endl; }; 
     void operator()(){} 
    }; 

    { //!!!!COPY CTOR of TFunctor is NEVER called in this scope 
     TFunctor myFunctor; 

     //TFunctor move ctor called here 
     function<void()> myStdFuncTemp{ std::move(myFunctor) }; 

     function<void()> myStdFunc{ move(myStdFuncTemp) }; 
    } 

    { //TFunctor copy ctor is called twice in this scope 
     TFunctor myFunctor; 

     //TFunctor copy ctor called once here 
     function<void()> myStdFuncTemp{ myFunctor }; 

     //TFunctor copy ctor called once here 
     function<void()> myStdFunc{ myStdFuncTemp }; 
    } 
} 

を、あなたはSTD ::機能と同じほとんどすべてを持っていますが、それは必要としないので、自身のコピーctorのを削除しunstd :: function_only_movableを作ることができますターゲット呼び出し可能オブジェクトに1つのコピーctorを持たせる必要があります。また、呼び出し可能なオブジェクトの右辺値からのみ構築する必要があります。

Thisは私の実装です。

+0

"レイヤーを押し下げて"コピー可能なラッパーを作成します。'std :: shared_ptr 'または 'const MyFunctor&'は 'operator()'を提供し、そのラッパーから 'std :: function'を構築します。 –

関連する問題