【问题标题】:Pros and Cons of usage of reference in case of PIMPL idiom在 PIMPL 成语的情况下使用参考的利弊
【发布时间】:2013-04-11 09:50:11
【问题描述】:

here 所述,在PIMPL idiom 的情况下,您可以使用引用 (d-reference) 而不是指针 (d-pointer)。

我正在尝试了解此实施是否存在任何严重问题以及利弊。

优点:

  • 由于使用了“.”,语法更短而不是“->”。
  • ...

缺点:

  • 如果 new ObjectPivate() 失败并且 new 没有抛出怎么办(例如:new(std::nothrow) 或自定义new) 并返回 nullptr?您需要实施额外的东西来检查引用是否有效。如果是指针,您只需使用:
if (m_Private) m_Private->Foo();
  • 在具有复杂初始化逻辑的 Object 的多个构造函数的极少数情况下,该解决方案可能不适用。 [© JamesKanze]
  • 使用指针进行内存管理更自然。 [© JamesKanze]
  • 需要考虑一些额外的实现细节(使用 swap())以确保异常安全(例如赋值运算符的实现)[© Matt Yang]
  • ...

这里是示例代码:

// Header file

class ObjectPrivate;

class Object
{
public:
 Object();
 virtual ~Object();

 virtual void Foo();

 private:
   ObjectPrivate&  m_Private;
};

// Cpp file

class ObjectPrivate
{
public:
  void Boo() { std::cout << "boo" << std::endl; }
};

Object::Object() :
m_Private(* new ObjectPrivate())
{
}

Object::~Object()
{
  delete &m_Private;
}

void Object::Foo()
{
  m_Private.Boo();
}

【问题讨论】:

  • 如果new ObjectPivate() 失败了,我想你应该想办法去throw。没有实现的 pimpl 毫无意义。
  • @juanchopanza 我不确定他所说的“失败”是什么意思。 new ObjectPrivate 调用 operator new(如果失败则抛出 bad_alloc)和构造函数(只能通过抛出异常来报告失败)。
  • @JamesKanze 对。我正在处理 OP 的 Cons 部分,但我想这种情况只有new (std::nothrow) 才有可能。

标签: c++ design-patterns


【解决方案1】:

这真的只是风格问题。我倾向于不使用 类中的引用开始,因此在 编译防火墙似乎更自然。但是有 通常无论哪种方式都没有真正的优势:new 可以 只有会因异常而失败。

您可能喜欢指针的一种情况是,当 对象有很多不同的构造函数,其中一些需要 在调用new 之前进行初步计算。在这个 情况下,可以用NULL初始化指针,然后调用 一个通用的初始化程序。我认为这种情况很少见, 然而。 (我遇到过一次,我记得。)

编辑:

只是另一种风格考虑:很多人不喜欢delete &amp;something; 之类的东西,如果您使用引用而不是指针,则需要它。同样,管理内存的对象使用指针似乎更自然(至少对我而言)。

【讨论】:

    【解决方案2】:

    我觉得写异常安全的代码不太方便。

    Object::operator=(Object const&amp;) 的第一个版本可能是:

    Object& operator=(Object const& other)
    {
        ObjectPrivate *p = &m_Private;
        m_Private = other.m_Private;        // Dangerous sometimes
        delete *p;
    }
    

    如果ObjectPrivate::operator=(ObjectPrivate const&amp;) 抛出异常是很危险的。那么使用临时变量呢?啊哈,没办法。如果要更改 m_Private,则必须调用 operator=()

    所以,void ObjectPrivate::swap(ObjectPrivate&amp;) noexcept 可以成为我们的救星。

    Object& operator=(Object const& other)
    {
        ObjectPrivate *tmp = new ObjectPrivate(other.m_Private);
        m_Private.swap(*tmp);                // Well, no exception.
        delete tmp;
    }
    

    然后考虑void ObjectPrivate::swap(ObjectPrivate&amp;) noexcept的实现。假设 ObjectPrivate 可能包含一个没有swap() noexceptoperator=() noexcept 的类实例。我觉得很难。

    好吧,这个假设太严格了,有时也不正确。即便如此,在大多数情况下,ObjectPrivate 也没有必要提供swap() noexcept,因为它通常是集中数据的辅助结构。

    相比之下,指针可以节省很多脑细胞。

    Object& operator=(Object const& other)
    {
        ObjectPrivate *tmp = new ObjectPrivate(*other.p_Private);
        delete p_Private;
        p_Private = tmp;        // noexcept ensured
    }
    

    如果使用智能指针会更优雅。

    Object& operator=(Object const& other)
    {
        p_Private.reset(new ObjectPrivate(*other.p_Private));
    }
    

    【讨论】:

      【解决方案3】:

      一些快速而明显的补充:

      专业版

      • 引用不能是0
      • 不能为引用分配另一个实例。
      • 由于变量更少,类职责/实现更简单。
      • 编译器可以进行一些优化。

      骗局

      • 不能为引用分配另一个实例。
      • 在某些情况下,参考将过于严格。

      【讨论】:

        猜你喜欢
        • 2017-11-12
        • 1970-01-01
        • 1970-01-01
        • 2014-06-21
        • 1970-01-01
        • 2012-03-14
        • 1970-01-01
        • 1970-01-01
        • 2018-06-14
        相关资源
        最近更新 更多