2

我想知道在访问智能指针方法之前为智能指针指定默认初始化还是进行NULL值检查更好?

目前我一直在使用下面的方法来避免调用increment()指针NULL这是一种合理的做事方式,还是有我看不到的陷阱?

注意:我们使用自定义智能指针类,并且我当前配置中没有 Boost 库来测试编译此代码。这应该编译,但 YMMV。

例子.h

#include <boost/shared_ptr.hpp>

class Foo
{
public:
  Foo() : mFoo(0) {}
  Foo(int rawValue) : mFoo(rawValue) {}

  void  increment() { mFoo++; }

private:
  int mFoo;
};

typedef boost::shared_ptr<Foo> FooSP;

class MyClass
{
public:
  MyClass() : mFoo(new Foo()) {}

  FooSP       foo() { return mFoo; }
  void        setFoo(FooSP newFoo) { mFoo = newFoo; }

private:
  FooSP mFoo;
};

主文件

#include <Example.h>

int main()
{
  MyClass temp;                    // Default-constructed
  temp.foo()->increment();         // Increment Foo's member integer
                                   // Before: mFoo = 0
                                   // After:  mFoo = 1
  FooSP tempFoo = new Foo(10);     // Create a Foo with a default size
  temp.setFoo(FooSP(new Foo(10))); // Explicitly set the FooSP member
  temp.foo()->increment();         // Increment the new FooSP
                                   // Before: mFoo = 10
                                   // After:  mFoo = 11
  return 0;
}
4

2 回答 2

3

如果您使用智能指针作为指针类型的一般替代品,则无法摆脱对 null 的检查。这是因为使用具有默认构造函数的智能指针定义的类可能允许使用其默认构造函数创建智能指针。动态地创建一个新对象只是为了填充指针直到你可以设置它似乎是一种资源的浪费。

shared_ptr的构造函数是显式的,因此您的初始化tempFoo不会编译。如果你想保存一行代码,你可以避免像这样声明临时代码:

temp.setFoo(FooSP(new Foo(10)));

也可以声明 setFoo获取常量引用的方法,避免在获取参数时操纵引用计数。

void setFoo(const FooSP &newFoo) { mFoo = newFoo; }

或者swap在参数实例上使用。

void setFoo(FooSP newFoo) { std::swap(mFoo, newFoo); }

如果我需要按照您的建议来实现某些东西,我将创建一个静态实例Foo作为空版本,然后让该increment方法在它是空版本时抛出异常。

class Foo
{
public:
  static Foo Null;
  //...
  void  increment() {
      if (this == &Null) throw Null;
      mFoo++;
  }
  //...
};

struct DeleteFoo {
    void operator () (Foo *t) const {
        if (t != &Foo::Null) delete t;
    }
};

class MyClass
{
public:
  MyClass() : mFoo(&Foo::Null, DeleteFoo()) {}
  //...
};

注意FooSP要正确处理的自定义删除器Foo::Null

于 2012-06-14T17:48:50.480 回答
2

在访问智能指针方法之前为智能指针指定默认初始化或执行 NULL 值检查更好吗?

没有适用于每种情况的正确答案(很快)。如果我不得不对一个或另一个犯错,我会在NULL没有默认初始化的情况下进行测试,因为这是一个明显的程序员错误,可以很容易地检测和纠正。

但是,我认为正确的答案是我们使用多个惯用语进行构造和初始化是有充分理由的,并且您应该为您的程序选择最佳方法

通常,我将在较低级别的类以及复杂的较高级别的类中显式(无默认或无默认初始化)。当类是中级并且默认值和所有权更明显(通常是因为用例有限)时,默认值可能是明智的。

通常,您只想保持一致,以避免让客户感到惊讶。您还需要了解分配默认初始化对象的复杂性。如果创建起来又大又复杂,并且默认设置没有意义,那么当默认构造的对象是错误的选择时,您只是在浪费大量资源。

  • a) 不要在没有意义的地方应用默认值。默认值应该很明显。
  • b) 避免浪费分配。

除了您提到的方法之外,您还可以考虑其他一些角度:

  • 匹配Foo中声明的构造函数MyClass。至少,那些属于MyClass.
  • 如果可复制且高效复制,则将 a 传递FooMyClass的构造函数。
  • Foo将容器(在这种情况下为智能指针)传递给'MyClass的构造函数以消除任何歧义,并为客户端提供根据需要构建(并在共享指针的情况下共享)的选项Foo

这是一种合理的做事方式,还是有我看不到的陷阱?

浪费分配。令人惊讶的结果。它可以限制功能。最明显、最广泛适用的问题是时间和资源消耗。

为了说明一些场景:

  • 假设 Foo 每次构建时都会读取一个 1MB 的文件。当需要构造参数并且默认值不是正确的选项时,必须再次读取该文件。无辜的默认设置会使所需的磁盘 io 翻倍。
  • 在另一种情况下,省略的构造参数可能是另一个大的或复杂的共享指针。如果不存在,Foo 可能会创建自己的 -- 当资源可以/应该被共享时。

构造函数参数通常非常重要,并且通常不应该从界面中删除。在某些情况下这样做当然没问题,但是随着包含对象的复杂性增加,这些便利可能会引入很多限制或引入很多不必要的分配和 CPU 时间。

在您的程序中使用这两种方法都很好。使用我概述的其他方法也很好。具体来说,对问题使用正确的方法是理想的——有多种方法可以实现理想的解决方案;你只需要在你的程序试图做的事情的背景下确定它是什么。所有这些方法都有各自的优点和缺点 - 通常与程序操作的上下文和公开的接口完美匹配。

于 2012-06-14T18:02:20.807 回答