在PIMPL习语中使用引用的利弊

Pros and Cons of usage of reference in case of PIMPL idiom

本文关键字:引用 PIMPL 习语      更新时间:2023-10-16

如前所述,对于PIMPL习语,您可以使用引用(d-reference)而不是指针(d-pointer)。

我想知道这个实现是否有什么严重的问题,利弊是什么。

优点:

  • 更短的语法,因为使用"."代替"->"。

缺点:

  • 如果new objectprivate ()失败且new不抛出(例如:new(std::nothrow)或custom new)并返回nullptr怎么办?你需要实现额外的东西来检查引用是否有效。如果是指针,你只需使用:
<>之前 if (m_Private) m_Private->Foo(); 之前
  • 对象具有复杂初始化逻辑的多个构造函数的罕见情况下,解决方案可能不适用。[©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可以只有会因异常而失败。

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

编辑:

只是另一个风格考虑:很多人不喜欢像delete &something;这样的东西,如果你使用引用而不是指针,这是需要的。同样,对象管理内存使用指针似乎更自然(至少对我来说)。

我认为编写异常安全代码不方便。

Object::operator=(Object const&)的第一个版本可能是:
Object& operator=(Object const& other)
{
    ObjectPrivate *p = &m_Private;
    m_Private = other.m_Private;        // Dangerous sometimes
    delete *p;
}

如果ObjectPrivate::operator=(ObjectPrivate const&)抛出异常是危险的。那么如何使用临时变量呢?啊哈,不可能。如果你想改变m_Private, operator=()必须被调用。

所以,void ObjectPrivate::swap(ObjectPrivate&) 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&) 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));
}

一些快速和明显的添加:

Pro

  • 引用不能是0
  • 该引用不能被分配给另一个实例。
  • 类的职责/实现更简单,因为变量更少。
  • 编译器可以做一些优化。

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