【问题标题】:Trying to write string class that can do move semantics from an std::string尝试编写可以从 std::string 移动语义的字符串类
【发布时间】:2012-07-26 17:54:54
【问题描述】:

我正在编写自己的字符串类,实际上只是为了学习和巩固一些知识。除了我想要一个使用带有 std::string 的移动语义的构造函数之外,我一切正常。

在我的构造函数中,我需要复制并清空 std::string 数据指针和其他内容,它需要保持为空但有效的状态,而不删除字符串指向的数据,我该怎么做?

到目前为止我有这个

class String
{
private:
char* mpData;
unsigned int mLength;
public:
String( std::string&& str)
    :mpData(nullptr), mLength(0)
    {
    // need to copy the memory pointer from std::string to this->mpData

    // need to null out the std::string memory pointer
    //str.clear();  // can't use clear because it deletes the memory
    }
~String()
{
delete[] mpData;
mLength = 0;
}

【问题讨论】:

标签: c++ c++11 dynamic-memory-allocation stdstring move-semantics


【解决方案1】:

没有办法做到这一点。 std::string 的实现是实现定义的。每个实现都是不同的。

此外,不能保证字符串将包含在动态分配的数组中。一些std::string 实现会执行小字符串优化,其中小字符串存储在std::string 对象本身内部。

【讨论】:

  • 所以不可能?在不同类型的类之间进行移动语义通常是一个坏主意吗? stl 如何在一个向量类的字符串中管理这个?
  • 一个vector<string> 只需要能够从另一个string 移动构造或移动分配一个stringstring 类通过其移动构造函数和移动赋值运算符来处理这个问题。跨类型移动有点奇怪,但如果您同时控制源类型和目标类型的实现,或者源类型提供某种方式从它移动(例如unique_ptr 提供了一个release 成员函数),这绝对是可能的.
  • 我真的很讨厌总结结论“没办法”。 C++ 为您提供了很多控制权,并且有状态分配器至少可以在一般std::basic_string 情况下对其进行管理。它只需要记住分配的块(公平假设一个basic_string在堆上不拥有多个块),自定义move-from只需要在clearing之前通知它所有权的变化basic_string.
  • @Potatoswatter:现在你不能再从任意字符串中移动了。
  • @Xeo 这不是一个实用的解决方案(好吧,它和任何自定义分配器一样糟糕,但也没有更糟),但提及你可以做什么比把它留在“不”更有建设性。 "
【解决方案2】:

以下实现实现了所要求的,但存在一定风险。

关于这种方法的注意事项:

  • 它使用 std::string 来管理分配的内存。在我看来,像这样分层分配是一个好主意,因为它减少了单个类试图完成的事情的数量(但由于使用了指针,这个类仍然存在与编译器生成的副本相关的潜在错误操作)。

  • 我取消了 delete 操作,因为它现在由 allocation 对象自动执行。

  • 如果使用mpData 修改底层数据,它将调用所谓的未定义行为。它是未定义的,如here 所示,因为标准说它是未定义的。不过,我想知道是否存在const char * std::string::data() 的行为与T * std::vector::data() 不同的实际实现——通过这种方式,这样的修改是完全合法的。通过data() 进行的修改可能不会反映在对allocation 的后续访问中,但根据this question 中的讨论,假设没有进一步的更改,此类修改似乎不太可能导致不可预测的行为通过allocation 对象制作。

  • 它真的为移动语义优化吗?这可能是实现定义的。它还可能取决于传入字符串的实际值。正如我在另一个答案中所指出的,移动构造函数提供了一种优化机制——但它不保证会发生优化。


class String
{
private:
char* mpData;
unsigned int mLength;
std::string allocation;
public:
String( std::string&& str)
    : mpData(const_cast<char*>(str.data())) // cast used to invoke UB
    , mLength(str.length())
    , allocation(std::move(str)) // this is where the magic happens
    {}
};

【讨论】:

    【解决方案3】:

    我将这个问题解释为“我可以让移动构造函数产生正确的行为吗”,而不是“我可以让移动构造函数以最佳方式运行吗”。 p>

    如果问题是严格的,“有没有一种可移植的方式从std::string中窃取内存”,那么答案是“没有,因为没有'transfer公共 API 中提供的内存所有权操作”


    this explanation of move semantics 的以下引用很好地总结了“移动构造函数”...

    C++0x 引入了一种称为“右值引用”的新机制, 除其他外,允许我们通过函数检测右值参数 超载。我们所要做的就是编写一个带有右值的构造函数 参考参数。在该构造函数中,我们可以做任何事情 需要源,只要我们让它处于某种有效状态。

    基于此描述,在我看来,您可以实现“移动语义”构造函数(或“移动构造函数”),而不必实际窃取内部数据缓冲区。 一个示例实现:

    String( std::string&& str)
        :mpData(new char[str.length()]), mLength(str.length())
        {
        for ( int i=0; i<mLength; i++ ) mpData[i] = str[i];
        }
    

    据我了解,移动语义的意义在于您可以提高效率如果您愿意。由于传入的对象是瞬态的,因此不需要保留其内容——因此窃取它们是合法的,但不是强制性的。也许,如果您不转移某些基于堆的对象的所有权,那么实现这一点是没有意义的,但它似乎应该是合法的。也许它可以用作垫脚石——你可以尽可能多地窃取有用的东西,即使这不是全部内容。

    顺便说一句,有一个密切相关的问题here 正在构建相同类型的非标准字符串,并包含一个用于 std::string 的移动构造函数。然而,该类的内部结构有所不同,建议 std::string 可能具有对内部移动语义的内置支持(std::string -> std::string)。

    【讨论】:

    • 如果您不移动动态内存,则只需使用引用。此外,您的代码可能需要一个空终止符,具体取决于类的实现。字符串(std::string& str)
    • 他试图做的比移动语义允许的更多,而不是更少。
    • 我发现这篇博文与我的回答一致:akrzemi1.wordpress.com/2011/08/30/move-constructor-qa
    • 来自您的博文:如果您只是在移动构造函数中实现常规复制,那么您确实满足了这两个约束,因此这样做是正确的;但是,这样做确实没有意义。如果您不能或不想提供比复制构造函数更有效的移动构造函数,则根本不要定义它。复制构造函数适用于所有情况。
    • @EddieV223:同意——我在回答中指出了这一点。但由于这“只是为了学习”,我想我会注意到我的回答中的一些细微差别。
    猜你喜欢
    • 2016-10-04
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 2016-03-29
    • 2014-03-19
    • 2018-03-21
    • 2013-12-01
    相关资源
    最近更新 更多