复制和移动儿童成语类?

Copy and Move Idiom in children classes?

class A
{
    std::string val1;
    A(std::string str) : val1(std::move(str)){}
};

class B: A
{
    B(std::string str) : A(str){}
};

这样的话,str会被复制两次,还是不会? 在儿童 类 中使用复制和移动习语的最佳方法是什么?

在这种情况下,确定的最佳方法是经常记录副本和移动。只需将 std::string 包装到自定义 class 或具有自定义复制和移动语义的结构中,如下所示:

class StringWrapper
{
public:
    std::string data;

    StringWrapper(const char* cstr)
        : data(cstr) {}

    // Copy constructor and assignment
    StringWrapper(const StringWrapper& other)
        : data(other.data)
    {
        std::cout << "Copy constructor!\n";
    }

    StringWrapper& operator=(const StringWrapper& other)
    {
        data = other.data;
        std::cout << "Copy assignment!\n";
        return *this;
    }

    // Move constructor and assignment
    StringWrapper(StringWrapper&& other)
        : data(std::move(other.data))
    {
        std::cout << "Move constructor!\n";
    }

    StringWrapper& operator=(StringWrapper&& other)
    {
        data = std::move(other.data);
        std::cout << "Move assignment!\n";
        return *this;
    }
};

然后用StringWrapper代替std::string的每次使用:

class A
{
public: // Public for testing
    StringWrapper val1;
    A(StringWrapper str) : val1(std::move(str)){}
};

class B: public A // Private inheritance should be avoided
{
public: // Public for testing
    B(StringWrapper str) : A(std::move(str)){}
};

经过小测试:

int main()
{
    StringWrapper strA("hello");
    A a(strA);
    std::cout << a.val1.data << '\n';

    StringWrapper strB("world");
    B b(strB);
    std::cout << b.val1.data << '\n';
}

然后分析输出:

Copy constructor!
Move constructor!
hello
Copy constructor!
Copy constructor!
Move constructor!
world

第一次复制发生在按值传递 strA 时。这是必要的,因为我们不希望对 strA 的更改影响 a.val1

第二个副本非常相似:它发生在按值传递 strB 时。出于同样的原因,这也是必要的。

另一方面,第三个副本是多余的。它发生在将 str 传递给 B 的构造函数中的父构造函数时。 str会在构建结束时销毁,所以我们最好移动它的内容而不是复制它:

class B: public A
{
public:
    B(StringWrapper str) : A(std::move(str)){} // Replaced copy with move
};

已修复!

Copy constructor!
Move constructor!
hello
Copy constructor!
Move constructor!
Move constructor!
world

如果您是 C++ 的新手,最好简单地调查一下如果您执行您的代码会发生什么,而不是做出可能错误的假设。

要查看发生了什么,只需将 std::string 替换为您自己的 class 并在其中放入一些调试输出。

struct mystring
{
    mystring() { std::cout << "Default" << std::endl; }

    mystring( mystring&& ) { std::cout << "Move" << std::endl; }
    mystring( const mystring& ) { std::cout << "Copy" << std::endl; }

    mystring& operator=( const mystring& ) { std::cout << "Copy assign" << std::endl; return *this; }
    mystring& operator=( mystring&& ) { std::cout << "Move assign" << std::endl; return *this; }

    ~mystring() { std::cout << "Delete" << std::endl; }
};

还有一些测试代码

int main()
{
    std::cout << "1" << std::endl;
    mystring ms;
    std::cout << "2" << std::endl;
    B b(std::move(ms));
    std::cout << "3" << std::endl;
}

您将看到:

1
Default
2
Copy
Copy
Move
Delete
Delete
3
Delete
Delete


您的问题“在这种情况下,str 是否会被复制两次?”被回答:是的,它复制了两次!

如果您还希望 forwarding/move 按预期工作,则必须始终遵循 rule of three/five/zero

这将导致:

class A
{
    private:
        mystring val1;

    public:
        A(): val1{}{}
        A( const mystring& str ): val1( str ) {} 
        A(mystring&& str) : val1(std::move(str)){}

        // if you also want to assign a string later
        A& operator=(const mystring& str ) { std::cout << "copy string to A" << std::endl; val1 = str; return *this; }
        A& operator=(mystring&& str ) { std::cout << "move string to A" << std::endl; val1 = (std::move(str)); return *this; }

        // and now all the stuff for copy/move of class itself:
        A( const A& a): val1{ a.val1} {}
        A( A&& a): val1{ std::move(a.val1)} {}
        A& operator=( const A& a) { std::cout << "copy from A" << std::endl; val1=a.val1; return *this; }
        A& operator=( A&& a) { std::cout << "move from A" << std::endl; val1=std::move(a.val1); return *this; }
};


class B: public A
{

    public:
        B(): A{}{}
        B( const mystring& str ): A( str ) {} 
        B(mystring&& str) : A(std::move(str)){}

        // and now all the stuff for copy/move of class itself:
        B( const B& b): A{ b} {}
        B( B&& b): A{ std::move(b)} {}
        B& operator=( const B& b) { std::cout << "copy from B" << std::endl; A::operator=(b); return *this; }
        B& operator=( B&& b) { std::cout << "move from B" << std::endl; A::operator=(std::move(b)); return *this; }
};

现在还要测试这个:

int main()
{
    std::cout << "1" << std::endl;
    mystring ms;
    std::cout << "2" << std::endl;
    B b(std::move(ms));
    std::cout << "3" << std::endl;

    //   
    std::cout << "4" << std::endl;
    B b2;

    // and now move assign
    std::cout << "5" << std::endl;
    b = std::move(b2);
    std::cout << "6" << std::endl;
}

结果:

1
Default
2
Move
3
4
Default
5
move from B
move from A
Move assign
6
Delete
Delete
Delete

现在第一步只有一个动作,动作分配也按预期工作。

看到了运行here