【问题标题】:How can I take ownership of a C++ std::string char data without copying and keeping std::string object?如何在不复制和保留 std::string 对象的情况下获得 C++ std::string char 数据的所有权?
【发布时间】:2025-12-16 05:30:01
【问题描述】:

如何在不复制和保留源 std::string 对象的情况下获得 std::string char 数据的所有权? (我想在不同类型之间使用移动语义。)

我使用 C++11 Clang 编译器和 Boost

基本上我想做一些与此等效的事情:

{
    std::string s(“Possibly very long user string”);
    const char* mine = s.c_str();

    // 'mine' will be passed along,
    pass(mine);

    //Made-up call
    s.release_data();

    // 's' should not release data, but it should properly destroy itself otherwise.
}

为了澄清,我确实需要进一步摆脱 std::string: 。该代码同时处理字符串和二进制数据,并且应该以相同的格式处理它。我确实想要来自 std::string 的数据,因为它来自另一个与 std::string 一起工作的代码层。

为了更深入地了解我想要这样做的地方:例如,我有一个异步套接字包装器,它应该能够从用户那里获取 std::string 和二进制数据进行写入。两个“API”写入版本(采用 std::string 或行二进制数据)在内部解析为相同的(二进制)写入。我需要避免任何复制,因为字符串可能很长。

WriteId     write( std::unique_ptr< std::string > strToWrite )
{

    // Convert std::string data to contiguous byte storage
    // that will be further passed along to other
    // functions (also with the moving semantics).
    // strToWrite.c_str() would be a solution to my problem
    // if I could tell strToWrite to simply give up its
    // ownership. Is there a way?

    unique_ptr<std::vector<char> > dataToWrite= ??

    //
    scheduleWrite( dataToWrite );
}

void scheduledWrite( std::unique_ptr< std::vecor<char> > data)
{
    …
}

std::unique_ptr 在这个例子中说明所有权转移:任何其他具有相同语义的方法对我来说都可以。

我想知道这种特定情况的解决方案(使用 std::string char 缓冲区)以及字符串、流和类似一般问题的此类问题:在字符串、流、std 容器和缓冲区类型之间移动缓冲区的技巧.

在不复制的情况下在不同 API/类型之间传递缓冲区数据时,我还希望获得有关 C++ 设计方法和特定技术的提示和链接。我提到但没有使用流,因为我在那个主题上很不稳定。

【问题讨论】:

  • 你不能,因为你无法安全地回收内存。在某一时刻你应该释放缓冲区,那么为什么不将字符串一直保持下去,它会自动执行此操作?
  • std::unique_ptr&lt;char[]&gt; 将是唯一允许类似的东西。
  • @minsk :我认为每个人都清楚你的方案,但你没有明白 - 这是不可能的。 ;-]
  • @minsk : 呃,size()length() - 它们都不关心嵌入的空值(如果你想要可靠的信息,请使用 cppreference 而不是 cplusplus.com :-]) .
  • @minsk : C++ 标准you 是错误的; §21.4.4/1:“返回:当前字符串中类似字符的对象的数量。”你不应该依赖单一的实现来获得正确的行为,你应该依赖强制行为的标准;标准库实现也有错误!

标签: c++ string c++11 buffer iostream


【解决方案1】:

如何在不复制和保留源 std::string 对象的情况下获得 std::string char 数据的所有权? (我想在不同类型之间使用移动语义)

你不能安全地做到这一点。

对于特定的实现,在某些情况下,您可能会做一些可怕的事情,例如使用别名来修改字符串中的私有成员变量,以诱使字符串认为它不再拥有缓冲区。但即使你愿意尝试这个,它也不会总是奏效。例如。考虑小字符串优化,其中字符串没有指向保存数据的某个外部缓冲区的指针,数据位于字符串对象本身内部。


如果您想避免复制,可以考虑将接口更改为 scheduleWrite。一种可能性是:

template<typename Container>
void scheduledWrite(Container data)
{
    // requires data[i], data.size(), and &data[n] == &data[0] + n for n [0,size)
    …
}

// move resources from object owned by a unique_ptr
WriteId write( std::unique_ptr< std::vector<char> > vecToWrite)
{
    scheduleWrite(std::move(*vecToWrite));
}

WriteId write( std::unique_ptr< std::string > strToWrite)
{
    scheduleWrite(std::move(*strToWrite));
}

// move resources from object passed by value (callers also have to take care to avoid copies)
WriteId write(std::string strToWrite)
{
    scheduleWrite(std::move(strToWrite));
}

// assume ownership of raw pointer
// requires data to have been allocated with new char[]
WriteId write(char const *data,size_t size) // you could also accept an allocator or deallocation function and make ptr_adapter deal with it
{
    struct ptr_adapter {
        std::unique_ptr<char const []> ptr;
        size_t m_size;
        char const &operator[] (size_t i) { return ptr[i]; }
        size_t size() { return m_size; }
    };

    scheduleWrite(ptr_adapter{data,size});
}

【讨论】:

  • @minsk:希望这样做是很合理的,不幸的是这是不可能的,因为该类的设计不允许这样做。
  • @minsk:你不知道应该如何释放缓冲区。由于没有release 成员,你无法用string 实现你想要的。
  • 这些都是好点:小字符串优化和知道如何释放另一个实现缓冲区。 std::stringstream 怎么样,我可以将 std::string 移动到暴露其缓冲区的 std::stringstream 中吗?这些都是std对象,并且std::stringstream知道std::string..我真的很想找到一个避免复制并允许部分代码使用字符串的解决方案:(
  • @Alexandre:我不想一直保留 std::string 因为我想在内部统一字符串或二进制数据的实现。否则我必须跟踪两个版本。
  • @minsk : "std::stringstream 怎么样,我能以某种方式将 std::string 移动到 std 流中吗?" 不,std::basic_stringbuf&lt;&gt; 通过 const 获取其字符串参数-参考。
【解决方案2】:

此类使用移动语义和 shared_ptr 获取字符串的所有权:

struct charbuffer
{
  charbuffer()
  {}

  charbuffer(size_t n, char c)
  : _data(std::make_shared<std::string>(n, c))
  {}

  explicit charbuffer(std::string&& str)
  : _data(std::make_shared<std::string>(str))
  {}

  charbuffer(const charbuffer& other)
  : _data(other._data)
  {}

  charbuffer(charbuffer&& other)
  {
    swap(other);
  }

  charbuffer& operator=(charbuffer other)
  {
    swap(other);
    return *this;
  }

  void swap(charbuffer& other)
  {
    using std::swap;
    swap(_data, other._data);
  }

  char& operator[](int i)
  { 
    return (*_data)[i];
  } 

  char operator[](int i) const
  { 
    return (*_data)[i];
  } 

  size_t size() const
  {
    return _data->size();
  }

  bool valid() const
  { 
    return _data;
  }

private:
  std::shared_ptr<std::string> _data;

};

示例用法:

std::string s("possibly very long user string");

charbuffer cb(std::move(s)); // s is empty now

// use charbuffer...

【讨论】:

  • 据我了解,被移动的字符缓冲区将保存一个空的 shared_ptr (与复制移动构造函数中默认构造的相同),因此当移动的字符缓冲区超出范围及其析构函数时它被调用然后什么都没有发生。
  • 你是 100% 正确的,不知道我现在在想什么。 :-P 抱歉打扰了。
  • 移动 ctor charbuffer(std::string&amp;&amp; str) 实际上并没有从字符串中移动。您在初始化过程中缺少对 std::move 的调用。应该是_data(std::make_shared&lt;std::string&gt;(std::move(str)))
【解决方案3】:

你可以使用多态来解决这个问题。基本类型是统一数据缓冲区实现的接口。然后你会有两个派生类。一个用于std::string 作为源,另一个使用您自己的数据表示。

struct MyData {
    virtual void * data () = 0;
    virtual const void * data () const = 0;
    virtual unsigned len () const = 0;
    virtual ~MyData () {}
};

struct MyStringData : public MyData {
    std::string data_src_;
    //...
};

struct MyBufferData : public MyData {
    MyBuffer data_src_;
    //...
};

【讨论】:

  • user315052 我标记了这个答案,因为它是一个解决方案并且感谢回答。但出于多种原因,我会避免这种方法,包括可能的虚拟继承命中、类型安全、管理问题;它进一步强加了一种数据类型(MyData)。可能会变得很麻烦。我必须对 data_src_ 进行某种独特的访问,除此之外,我必须使用新的 MyData 并将其包装(将其传递给其他线程)。如果我必须使用包装器,我宁愿使用不带虚拟的侵入性较小且更安全的方法,bames53 在第一个答案中建议
最近更新 更多