【问题标题】:C++ 11 Move SemanticsC++ 11 移动语义
【发布时间】:2015-03-24 14:47:48
【问题描述】:

我试图了解 C++ 11 移动语义的工作原理。我已经实现了一个类,它包装了一个指向 String 对象的指针,但是移动构造函数和移动赋值运算符都没有按预期调用。

我通过 Eclipse CDT 使用 GCC 4.7.2:

你能帮我理解原因吗?

#include <iostream>
#include <string>
#include <utility>

using namespace std;

class StringPointerWrapper {
public:

    // Default constructor with default value
    StringPointerWrapper(const std::string& s = "Empty"): ps(new std::string(s)) {
        std::cout << "Default constructor: " << *ps << std::endl;
    }

    //Copy constructor
    StringPointerWrapper(const StringPointerWrapper& other): ps(new std::string(*other.ps)) {
        std::cout << "Copy constructor: " << *other.ps << std::endl;
    }

    //Copy assignment operator
    StringPointerWrapper& operator=(StringPointerWrapper other) {
        std::cout << "Assignment operator (ref): " << *other.ps << std::endl;
        swap(ps, other.ps);
        return *this;
    }


    //Alternate copy assignment operator
    /*StringPointerWrapper& operator=(StringPointerWrapper& other) {
        std::cout << "Assignment operator (val)" << std::endl;
        //We need to do the copy by ourself
        StringPointerWrapper temp(other);
        swap(ps, temp.ps);
        return *this;
    }*/

    //Move constructor
    StringPointerWrapper(StringPointerWrapper&& other) noexcept : ps(nullptr) {
        std::cout << "Move constructor: " << *other.ps << std::endl;
        ps = other.ps;
        other.ps = nullptr;
    }

    //Move assignment operator
    StringPointerWrapper& operator= (StringPointerWrapper&& other) noexcept {
        std::cout << "Move assignment operator: " << *other.ps << std::endl;
        if(this != &other) {
            delete ps;
            ps = other.ps;
            other.ps = nullptr;
        }
        return *this;
    }


    //Destructor
    ~StringPointerWrapper() {
        std::cout << "Destroying: " << *this << std::endl;
        delete ps;
    }
private:
        friend std::ostream& operator<<(std::ostream& os, StringPointerWrapper& spw) {
            os << *spw.ps;
            return os;
        }
        std::string *ps;
};


int main(int argc, char *argv[]) {

    StringPointerWrapper spw1("This is a string");
    StringPointerWrapper spw2;
    StringPointerWrapper spw3("This is another string");
    StringPointerWrapper spw4 = {"This is a const string"};
    StringPointerWrapper spw5(StringPointerWrapper("String for move constructor"));
    std::cout << "spw2 before: " << spw2 << std::endl;
    spw2 = spw3;
    std::cout << "spw2 after: " << spw2 << std::endl;
    StringPointerWrapper spw6 = StringPointerWrapper("String for move assignment");
    std::cout << spw1 << std::endl;
    std::cout << spw2 << std::endl;
    std::cout << spw3 << std::endl;
    std::cout << spw4 << std::endl;
    std::cout << spw5 << std::endl;
    std::cout << spw6 << std::endl;
}

【问题讨论】:

  • 首先,这可能不是理解移动语义的最佳示例。 std::string 基本上是 char * 的包装器,因此您正在围绕指向 char 数组指针的包装器的指针构建包装器。
  • 你能给出输出吗?
  • 输出为: 默认构造函数:这是一个字符串 默认构造函数:空 默认构造函数:这是另一个字符串 默认构造函数:这是一个常量字符串 默认构造函数:用于移动构造函数 spw2 之前的字符串:空副本构造函数:这是另一个字符串 赋值运算符(ref):这是另一个字符串 销毁:空 spw2 after:这是另一个字符串 默认构造函数:用于移动赋值的字符串 这是一个字符串 这是另一个字符串 这是另一个字符串 这是一个 const 字符串移动构造函数的字符串 移动赋值的字符串 所有调用的析构函数

标签: c++ c++11 constructor variable-assignment move-semantics


【解决方案1】:

没有调用移动构造函数,因为编译器忽略了构造函数作为优化。如果您在编译器调用中传递-fno-elide-constructors,则可以禁用它。

但是,您遇到了问题,因为您的移动构造函数只是使用来自other 的指针,该指针很快就会被删除。将std::string 用作指针并没有什么意义,您应该直接持有它并在移动赋值运算符和移动构造函数中调用std::move

【讨论】:

  • 现在调用了移动构造函数,但没有调用移动赋值运算符。
  • 那是因为除了spw2 = spw3 之外,您在示例中没有做任何作业。 T t = T{} 是复制初始化,不是复制赋值。
  • 此外,您的赋值运算符在使用 r 值调用时是不明确的。
  • 这个怎么样:StringPointerWrapper spw6 = StringPointerWrapper("String for move assignment");
  • 您最好的选择是删除移动赋值运算符。因为您已经有一个基于复制和交换的赋值运算符和一个移动构造函数,所以无论如何您都在使用移动语义,因为编译器将在创建副本时调用移动构造函数。
【解决方案2】:

StringPointerWrapper spw5(StringPointerWrapper("移动构造函数的字符串"));

这会调用默认构造函数,因为编译器已决定通过不创建临时构造来优化它(即,它已决定执行StringPointerWrapper spw5("String for move constructor"))。而是通过执行StringPointerWrapper spw5(std::move(StringPointerWrapper("String for move constructor"))); 来强制移动。

StringPointerWrapper spw6 = StringPointerWrapper("用于移动赋值的字符串");

再次,编译器通过优化临时的创建来调用默认构造函数。

注意:您的operator&lt;&lt; 需要防范null 指针。例如,

friend std::ostream& operator<<(std::ostream& os, StringPointerWrapper& spw) {
    if (spw.ps)
         os << (spw.ps);
        else
            os << "null";
    return os;
 }

【讨论】:

    【解决方案3】:

    你需要告诉编译器自己用 std::move 移动东西

    尝试:

     StringPointerWrapper spw5(std::move(StringPointerWrapper("String for move constructor")));
    

    【讨论】:

    • 您好,谢谢您的回答。应用建议的更改程序会崩溃。 :(
    • 编译器决定移动什么。它移动 r 值。 std::move 基本上将左值转换为右值,因此移动语义可以发挥作用。std::move 不进行移动(非常糟糕的名字..)
    • 无论如何我的期望是 StringPointerWrapper("String for move constructor")) 的返回值是一个非常量右值引用。因此,它应该绑定到复制构造函数和移动构造函数。无论如何,复制构造函数有一个 const 左值引用(const StringPointerWrapper& 其他),因此重载决议应该选择具有非 const 右值引用(StringPointerWrapper&& 其他)的移动构造函数。我错了吗?
    • @salvo 崩溃前控制台打印什么?
    • 崩溃前的输出是: 默认构造函数:这是一个字符串 默认构造函数:空 默认构造函数:这是另一个字符串 默认构造函数:这是一个常量字符串 默认构造函数:用于移动构造函数的字符串 移动构造函数:移动构造函数的字符串
    猜你喜欢
    • 1970-01-01
    • 2014-08-16
    • 2019-01-02
    • 2014-03-18
    • 2023-03-26
    • 1970-01-01
    • 2011-06-26
    相关资源
    最近更新 更多