【问题标题】:Setters for heap-allocated objects [closed]堆分配对象的设置器[关闭]
【发布时间】:2018-12-20 12:04:52
【问题描述】:

建议有一个由堆中分配的对象组成的类。

class A
{
public:
    A() { m_b = new B; m_c = new C; }
    virtual ~A() { delete m_b; delete m_c; }

    inline B* b() const { return m_b; }
    inline C* c() const { return m_c; }

private:
    B* m_b;
    C* m_c;
}

哪些设置器最适合此类代码?

我想出了这个,

inline void setB(B* b) const { delete m_b; m_b = b; }
inline void setC(C* c) const { delete m_c; m_c = c; }

但是有一个问题。如果我们添加了一个非堆变量或者只是一个我们不必用这个 setter 删除的变量,它将在下一次调用 setter 后被删除,这种情况会导致错误或意外行为。

我们也不能直接删除对象,因为 getter 有 const 修饰符。此外,它也不安全,因为该类的用户可能不知道内部对象是否分配在堆中。

能否请您解释一下如何将 setter 与堆分配的对象一起使用?

【问题讨论】:

  • 与任何其他对象没有什么不同。您有责任管理对象以避免内存泄漏和未定义的行为。不幸的是,没有一个可以按下的神奇按钮,并且始终使这项工作正常进行。没有通用的法则告诉您“如何将 setter 与堆分配的对象一起使用”。这是你需要自己弄清楚的事情。
  • 这是一些非常混乱的所有权语义。找出谁应该拥有什么,其余的随之而来。
  • 不要编写依赖于堆分配对象的类。编写这样的课程的好理由很少,但有很多不好的理由。如果你解释了为什么你觉得需要这样一个类,那么也许有人可以解释一种更好的方法来设计你的代码。
  • 1:不要使用术语“堆”分配的对象,它在 C++ 中没有用(C++ 不是 Java)。有四种类型的对象(自动/静态/动态/线程)存储持续时间。 * 也不一定意味着动态分配。 2:Get/Set中断封装更喜欢寻找更好的方式;通常是基于action 的方法。 3:不要返回这样的指针,它没有定义对象的所有权语义(因此泄漏或错误删除)。 4:我假设您没有遵守 3/5 规则,因为问题的示例很短。

标签: c++ memory memory-management heap-memory getter-setter


【解决方案1】:

不要使用原始的newdelete。改用智能指针——它们更安全、更容易使用。在您的情况下,std::unique_ptr 看起来是个不错的候选人:

class A
{
public:
    A() : m_b{std::make_unique<B>()}, m_c{std::make_unique<C>()} { }
    virtual ~A() = default;

    inline B* b() const { return m_b.get(); }
    inline C* c() const { return m_c.get(); }

private:
    std::unique_ptr<B> m_b;
    std::unique_ptr<C> m_c;
}

你的设置者可以简单地是:

void setB(std::unique_ptr<B>&& b) { m_b = std::move(b); }
void setC(std::unique_ptr<C>&& c) { m_c = std::move(c); }

【讨论】:

  • 我们至少可以建议geters 应该返回引用。或者指出返回可能被删除的指针的危险。您可能还需要注意,这会更改原始类,因为它不再是可复制的(尽管这确实修复了一个潜在的错误)。
  • @MartinYork:我不知道他在用吸气剂做什么。也许他需要原始指针?我试图让它尽可能接近 OP。我不明白您评论中的第二句话。
  • 他总是可以通过获取引用的地址从引用中获取指针。也不要求您更改代码只是为了说明这是一个坏主意。
  • @MartinYork 程序员可以像删除指针一样删除引用(在应用 addressof 运算符之后)。返回引用的问题是,如果指针为空,则可能存在 UB。
  • 他们可以。但是通过返回一个引用,您表明您没有返回所有权(因此删除不是您的责任,因此不太可能)。返回指针意味着所有者是谁的未知语义,因此很可能由于删除而导致错误。
【解决方案2】:

遵循 0 的规则。

class A {
public:
  A()=default;
  virtual ~A() = default;

  B* b() const { return m_b.get(); }
  C* c() const { return m_c.get(); }

  void set_b(std::unique_ptr<B> in){m_b=std::move(in);}
  void set_c(std::unique_ptr<C> in){m_c=std::move(in);}
private:
  std::unique_ptr<B> m_b=std::make_unique<B>();
  std::unique_ptr<C> m_c=std::make_unique<C>();
};

独特的 ptr 既使所有权清晰,修复了您不知道的错误,又使您的代码不那么脆弱。

已修复的错误:

  1. 在您的原始代码中,如果new C 抛出,您将泄漏B

  2. 如果您复制或移动原始对象,它们会双重删除

如果你想混合拥有和无主的子对象,第一件事就是不要这样做。第二个是使用更高级的智能指针。但是类型应该有明确的所有权语义,“也许拥有也许不拥有”是代码异味,会导致错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-02-04
    • 2012-01-10
    • 1970-01-01
    • 2013-01-25
    • 1970-01-01
    • 2010-12-10
    • 1970-01-01
    • 2013-05-10
    相关资源
    最近更新 更多