具有指向 std::optional 中值的视图的值更正

Value corrruption with a view pointing to a value within an std::optional

提问人:jerin 提问时间:10/9/2023 更新时间:10/9/2023 访问量:51

问:

我正在尝试创建一个设置,其中 C++ 类的实例可以选择拥有数据。为此,我正在创建一个 ,以及一个视图,以显示这个可选的视图(或从外部提供的东西)。考虑以下代码片段,这是我在解决此问题时遇到的问题的最小可重现示例:std::optional<Type>std::optional

#include <algorithm>
#include <iostream>
#include <optional>
#include <string_view>

class Wrap {
 public:
  Wrap(int value, size_t size) : size_(size) {
    value_ = new int[size];
    std::fill(value_, value_ + size_, value);
    std::cout << "new Wrap(" << (void*)value_ << ", " << size_ << ")" << std::endl;
  }

  ~Wrap() {
    std::cout << "delete Wrap(" << (void*)value_ << ", " << size_ << ")" << std::endl;
    delete[] value_;
    size_ = 0;
  }

  const int *data() const { return value_; }
  size_t size() const { return size_; }

 private:
  int *value_;
  size_t size_;
};

template <class T>
struct Pair {
  T first;
  T second;
};

Pair<Wrap> wrap_from(int first, int second, size_t size) {
  return {
      .first = Wrap(first, size),   //
      .second = Wrap(second, size)  //
  };
}

Pair<std::string_view> view_from(const Pair<Wrap> &wrap) {
  return {
      .first = std::string_view(                               //
          reinterpret_cast<const char *>(wrap.first.data()),   //
          wrap.first.size()                                    //
          ),                                                   //
      .second = std::string_view(                              //
          reinterpret_cast<const char *>(wrap.second.data()),  //
          wrap.second.size()),                                 //
                                                               //
  };
}

class Holder {
 public:
  Holder(int first, int second, size_t size)
      : wrap_(wrap_from(first, second, size)), view_(view_from(*wrap_)) {
    std::cout << "access first(" << (void*)view_.first.data() << ", " << view_.first.size() << ")" << std::endl;
    std::cout << "access second(" << (void*)view_.second.data() << ", " << view_.second.size() << ")" << std::endl;

    std::cout << "first: " << *(int*)view_.first.data() << std::endl;
    std::cout << "second: " << *(int*)view_.second.data() << std::endl;
  }

 private:
  using WrapPair = Pair<Wrap>;
  std::optional<WrapPair> wrap_;
  Pair<std::string_view> view_;
};

int main() {
  Holder holder(3, 4, 10);  
  return 0;
}

此代码提供以下输出(请参阅 https://godbolt.org/z/3r1MrYY7P):

Program returned: 139
Program stdout

new Wrap(0x563f294a62b0, 10)
new Wrap(0x563f294a72f0, 10)
delete Wrap(0x563f294a72f0, 10)
delete Wrap(0x563f294a62b0, 10)
access first(0x563f294a62b0, 10)
access second(0x563f294a72f0, 10)
first: 692744944
second: 0
delete Wrap(0x563f294a72f0, 10)

Program stderr

free(): double free detected in tcache 2
Program terminated with signal: SIGSEGV

我试图把我的头绕在调用析构函数的位置,使析构函数中的值变得无用。双重释放似乎也在发生。我预计,由于其中大多数都是返回类型,因此移动语义和复制省略开始发挥作用。std::optional

我做错了什么?如何缓解这种情况?有没有其他方法可以达到预期的效果?

C++ std可选

评论

4赞 PaulMcKenzie 10/9/2023
您的类违反了 3 规则。转到该链接的“管理资源”部分,你将看到与你的类相似的示例,但错误基本相同。Wrap
2赞 PaulMcKenzie 10/9/2023
我预计,由于其中大多数都是返回类型,因此移动语义和复制省略开始发挥作用。-- 你的班级应该在它发现自己所处的任何情况下工作,关于复制和作业。永远不要预测你认为编译器应该做什么,特别是如果你预期的是编译器将要做的事情。

答:

2赞 PaulMcKenzie 10/9/2023 #1

您的类违反了 3 规则Wrap

添加缺少的功能:

  Wrap(const Wrap& rhs) : value_(new int[rhs.size_])
  {
     std::copy(rhs.value_, rhs.value_ + rhs.size_, value_);
     size_ = rhs.size_;
  }
  
  Wrap& operator=(Wrap rhs)
  {
    std::swap(rhs.size_, size_);
    std::swap(rhs.value_, value_);
    return *this;
  }

运行时错误现已得到缓解。

现场示例

正在完成的复制的更好说明是在构造函数和析构函数中输出 的值和其他信息:this

实况 2