8

考虑这些类:

#include <iostream>
#include <string>

class A
{
    std::string test;
public:
    A (std::string t) : test(std::move(t)) {}
    A (const A & other) { *this = other; }
    A (A && other) { *this = std::move(other); }

    A & operator = (const A & other)
    {
        std::cerr<<"copying A"<<std::endl;
        test = other.test;
        return *this;
    }

    A & operator = (A && other)
    {
        std::cerr<<"move A"<<std::endl;
        test = other.test;
        return *this;
    }
};

class B
{
    A a;
public:   
    B (A && a) : a(std::move(a)) {}
    B (A const & a) : a(a) {}
};

创建 时B,我总是有一个最佳的正向路径A,一个右值的移动或一个左值的副本。

是否可以使用一个构造函数实现相同的结果?这种情况下问题不大,但是多参数呢?我需要参数列表中每个可能出现的左值和右值的组合。

这不仅限于构造函数,还适用于函数参数(例如 setter)。

注意:这个问题严格来说是关于class B; class A存在只是为了可视化复制/移动调用是如何执行的。

4

3 回答 3

9

“按价值”方法是一种选择。它不像您所拥有的那样最佳,但只需要一个重载:

class B
{
    A a;
public:   
    B (A _a) : a(move(_a)) {}
};

左值和 xvalue 的成本都是 1 次额外的移动构造,但这对于纯右值(1 次移动)仍然是最佳的。“xvalue”是已使用 std::move 强制转换为右值的左值。

您还可以尝试“完美转发”解决方案:

class B
{
    A a;
public:   
    template <class T,
              class = typename std::enable_if
              <
                 std::is_constructible<A, T>::value
              >::type>
    B (T&& _a) : a(std::forward<T>(_a)) {}
};

这将使您回到最佳数量的复制/移动结构。但是您应该限制模板构造函数,使其不会过于通用。您可能更喜欢使用 is_convertible 而不是 is_constructible ,就像我在上面所做的那样。这也是一个单一的构造函数解决方案,但是随着您添加参数,您的约束变得越来越复杂。

注意:上面的约束是必要的,因为没有约束,客户端B在查询时会得到错误的答案std::is_constructible<B, their_type>::value。如果没有适当的约束,它会错误地回答 true B

我会说这些解决方案中没有一个总是比其他解决方案更好。这里需要进行工程权衡。

于 2012-05-06T16:45:08.200 回答
2

为构造函数使用推导的参数类型B

template <typename T> explicit B(T && x) : a(std::forward<T>(x) { }

这适用于A可以构造对象的任何参数。

如果A有多个具有不同数量参数的构造函数,您可以通过...在任何地方添加来使整个事情变得可变。

不过,正如@Howard 所说,您应该添加一个约束,以便该类似乎不能从它实际上不是的参数构造。

于 2012-05-06T16:38:00.030 回答
1

如果string您的示例中的 是std::string,则根本不关心:默认提供的 copy 和 move 调用它们各自的 in 成员。并std::string实现了复制和移动,因此临时对象被移动,变量被复制。

无需定义特定的复制和移动 ctor 和分配。你可以离开构造函数

A::A(string s) :test(std::move(s)) {}

一般来说,复制和移动的简单实现可以如下

class A
{
public:
    A() :p() {}

    A(const A& a) :p(new data(*a.p)) {} //copy
    A(A&& a) :p(a.p) { a.p=0; }         //move

    A& operator=(A a) //note: pass by value
    { clear(); swap(a); return *this; }
    ~A() { clear(); }

    void swap(A& a) { std::swap(p,a.p); }
    void clear() { delete p; p=0; }

private:

    data* p;
};

operator=接受一个在内部移动的值。如果它来自一个临时的被移动,如果它来自一个变量被复制。复制和移动之间的区别需要不同的构造函数,但是,如果我们将 A 派生为

class B: public A
{
...
};

不需要覆盖任何东西,因为 B 的默认 copy-ctor 调用 A 的副本,B 的默认移动调用 A 的移动,并且 B 的所有默认分配运算符都调用为 A 定义的唯一一个(移动或复制取决于已转发的内容)。

于 2012-05-06T16:54:03.733 回答