【问题标题】:Properly return a unique ptr正确返回一个 unique_ptr
【发布时间】:2016-07-22 08:17:45
【问题描述】:

我正在编写一个字符串类MyString(是的,作为家庭作业)并且必须提供一个返回unique_ptr<char[]>(而不是Vector)的toCString 方法。不幸的是,当将指针返回给调用者时我失败了:结果总是充满了错误的内容 - 似乎我在堆栈上创建了指针和/或字符数组。

unique_ptr<char[]> MyString::toCString() const {
     char *characters = new char[m_len];
     char *thisString = m_string.get();
     for (int i = 0; i < m_len; i++) {
         characters[i] = *(thisString + m_start + i);
     }
     const unique_ptr<char[], default_delete<char[]>> &cString = unique_ptr<new char[m_len]>(characters);
     return cString;
}

在调试时,我总是得到预期的行为。问题仅发生在呼叫者站点上。我的错在哪里?

【问题讨论】:

  • 为什么要返回智能指针? C 字符串只是指向char(而不是数组)的指针
  • unique_ptr&lt;new char[m_len]&gt;(characters) 这没有意义,也无法编译。显示您的实际代码。
  • 您在堆栈上分配智能指针作为引用,然后退出函数。即使该行为似乎是正确的,这也会产生不可预测的行为。 stackoverflow.com/questions/10643563/…
  • @NathanielJohnson 该函数按值返回-正如您引用的文章所推荐的那样。它不返回引用或指向局部变量的指针。问题出在其他地方。
  • 它是参考而不是副本。

标签: c++ c++11


【解决方案1】:

我看到已经有一个接受的答案,但这并不能解决问题。客户端的问题正在发生,因为您没有以空值终止 c 字符串。

我不知道 m_string 是什么类型,所以暂时假设它是一个 std::string。你可以自己翻译实际的方法:

std::unique_ptr<char[]> MyString::toCString() const 
{
    // get length (in chars) of string
    auto nof_chars = m_string.size();

    // allocate that many chars +1 for the null terminator.
    auto cString = std::unique_ptr<char[]>{new char[nof_chars + 1]};

    // efficiently copy the data - compiler will replace memcpy
    // with an ultra-fast sequence of instructions in release build
    memcpy(cString.get(), m_string.data(), nof_chars * sizeof(char));

    // don't forget to null terminate!!
    cString[nof_chars] = '\0';

    // now allow RVO to return our unique_ptr
    return cString;
}

根据 Christophe 的建议,这里又是方法,用 std::copy_n 编写。请注意,std::copy[_xxx] 函数套件都返回一个迭代器,该迭代器在最后一次写入之后进行寻址。我们可以使用它来节省重新计算空终止符的位置。标准库是不是很棒?

std::unique_ptr<char[]> MyString::toCString() const 
{
    // get length (in chars) of string
    auto nof_chars = m_string.size();

    // allocate that many chars +1 for the null terminator.
    auto cString = std::unique_ptr<char[]>{new char[nof_chars + 1]};

    // efficiently copy the data - and don't forget to null terminate
    *std::copy_n(m_string.data(), nof_chars, cString.get()) = '\0';

    // now allow RVO to return our unique_ptr
    return cString;
}

【讨论】:

  • 显然该对象表示m_string 的子字符串,因此是m_startm_len。但是你关于腾出空间和添加 NUL 终止符是绝对正确的。
  • 替换old memcpy() with a std::copy() or std::copy_n() 的使用不是有意义吗?
  • @Christophe 同意,这样会更地道。
  • @Christophe 您的建议已添加为 toCString 的第二个版本。我认为你的想法更好。
【解决方案2】:

不要像以前那样创建对 unique_ptr 的引用。相反,直接返回 unique_ptr:移动构造函数将处理所有事情:

 return unique_ptr<char[], default_delete<char[]>>(characters);

【讨论】:

  • 还要确定c++编译器支持并启用c++11。
  • @NathanielJohnson 确定!另一方面,如果 OP 使用 unique_ptr 而不是已弃用的 auto_ptr 并且已经可以用它编译一些代码,我可以假设给出了 C++11 ;-)
  • 您的意思是,“返回值优化将处理一切”。按值高效返回甚至不需要移动构造函数。
  • @RichardHodges 是的,确实:返回值的复制/移动省略规则可能会使情况变得更好!但是我没有在回答中提到它,因为移动构造是真正的推动者,根据 12.8/31,实现可以进行复制/移动省略,但没有义务这样做。
  • @Christophe 明白了。仅供参考,委员会有一项提案要求在 c++17 中进行复制省略。希望它通过!
【解决方案3】:

既然你已经编辑了你的问题,现在你正在使用

unique_ptr<char[]> cString = unique_ptr<char[]>{new char[m_len]};

第一个改进:使用自动

auto cString = unique_ptr<char[]>{new char[m_len]};

第二个改进:你的标签是C+11,但如果你碰巧用的是C+14,那么就用std::make_unique这样:

auto cString = std::make_unique<char[]>(m_len);

此外,正如 Scott Meyers 所说,如果您使用 C+11,那么只需自己编写 make_unique 函数即可。不难,而且非常好用。

http://ideone.com/IIWyT0

template<class T, class... Types>
inline auto make_unique(Types&&... Args) -> typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
{
    return (std::unique_ptr<T>(new T(std::forward<Types>(Args)...)));
}

template<class T>
inline auto make_unique(size_t Size) -> typename std::enable_if<std::is_array<T>::value && std::extent<T>::value == 0, std::unique_ptr<T>>::type
{
    return (std::unique_ptr<T>(new typename std::remove_extent<T>::type[Size]()));
}

【讨论】:

    猜你喜欢
    • 2023-03-17
    • 2014-10-25
    • 2019-11-13
    • 2012-08-31
    • 2013-05-30
    • 2011-04-16
    • 2018-04-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多