2

当我遇到这种情况时,我正在学习 C++,我想在 C++ 中实现以下 C 符号代码的等效有效版本。

<header.h>
struct Obj;
Obj* create(...);
void do_some_thing(Obj*);
void do_another_thing(Obj*);
void destroy(Obj*);

要求是:

  • 该实现在库(静态/动态)中提供,并且标头不公开除接口以外的任何细节
  • 它应该同样有效

使用虚函数公开接口(类 COM)是不合格的;这是一种启用多态性(通过同一接口公开多个实现)的解决方案,但事实并非如此,而且由于我不需要它带来的价值,我不明白为什么我应该支付调用函数的成本通过2个间接指针。所以我的下一个想法是 pimpl 成语:

<header.h>
class Obj {
public:
  Obj();
  ~Obj();
  void do_some_thing();
  void do_another_thing();
private:
  class Impl;
  smart_ptr<Impl>  impl_; // ! What should I use here, unique_ptr<> or shared_ptr<> ?
};

shared_ptr<> 似乎不符合条件,我会为原始实现中不存在的不必要的互锁递增/递减付费。另一方面, unique_ptr<> 使 Obj 不可复制。这意味着客户端不能调用他自己的函数来获取 Obj 的值,而 Obj 只是一个指针的包装器,所以本质上他不能通过值传递指针!他可以在原始版本中做到这一点。(通过引用传递仍然不合格:他仍在传递指向指针的指针)

那么在 C++ 中实现这一点的同样有效的方法应该是什么?

编辑:我给了它更多的想法,我来到了这个解决方案:

<header.h>
class ObjRef // I exist only to provide an interface to implementation
{            //  (without virtual functions and their double-level indirect pointers)
public:
  ObjRef();
  ObjRef(ObjRef);           // I simply copy pointers value
  ObjRef operator=(ObjRef); // ...
  void do_some_thing();
  void do_another_thing();
protected:
  class Impl;
  Impl*  impl_; // raw pointer here, I'm not responsible for lifetime management
};

class Obj : public ObjRef
{
  Obj(Obj const&);            // I'm not copyable
  Obj& operator=(Obj const&); // or assignable 
public:
  Obj(Obj&&);                 // but I'm movable (I'll have to implement unique_ptr<> functionality)
  Obj& operator=(Obj&&);
  Obj();
  ~Obj(); // I destroy impl_
  // and expose the interface of ObjRef through inheritance
};

现在我回到客户端Obj,如果客户端需要通过调用他的一些其他函数来分配Obj的使用,他可以将它们声明为

void use_obj(ObjRef o);

// and call them:
Obj o = call_my_lib_factory();
use_obj(o);
4

2 回答 2

0

为什么不保留C原始?您不必在 C 版本中支付引用计数溢价的原因是 C 版本依赖调用者来记录Obj*正在使用的副本数。

通过尝试确保替换是可复制的,并确保destroy仅在最后一个引用被销毁后才调用底层方法,您对原始方法施加了额外的要求,因此正确的解决方案是很自然的(在我看来be shared_ptr) 比原版有一些有限的额外费用。

于 2013-03-10T12:55:12.637 回答
-1

我认为首先需要指向对象的指针是有原因的。因为最简单、最有效的方法就是在堆栈上创建它:

{
  Obj x(...);
  x.do_some_thing();
  x.do_another_thing();
} // let the destructor handle `destroy()` when the object goes out of scope

但是假设您需要在堆上创建它,无论出于何种原因,大部分都一样简单,同样高效:

{
  std::unique_ptr<Obj> x = Obj::create(...); // if you want a separate `create` function
  std::unique_ptr<Obj> x = new Obj(...); // Otherwise, a plain constructor will do

  x->do_some_thing();
  x->do_another_thing();
} // as before, let the destructor handle destruction

不需要继承、接口或虚函数。您正在编写 C++,而不是 Java。C++ 的基本规则之一是“不要为不使用的东西付费”。默认情况下,C++ 的性能与 C 一样。您不必做任何特别的事情来获得良好的性能。如果您不需要它们,您所要做的就是不要使用具有性能成本的功能。

您在 C 版本中不需要引用计数,那么为什么要在 C++ 版本中使用引用计数(带有shared_ptr)?您不需要虚拟函数在 C 版本中提供的功能(它将通过函数指针实现),那么为什么要在 C++ 函数中创建虚拟函数呢?

但是 C++ 可以让您特别整理创建/销毁的东西,这不会花费您任何费用。所以用那个。在其构造函数中创建对象,并让其析构函数销毁它。只需将对象放置在适当的范围内,这样当您希望将其销毁时,它就会超出范围。它为您提供了更好的语法来调用“成员”函数,因此也可以使用它。

另一方面, unique_ptr<> 使 Obj 不可复制。这意味着客户端不能调用自己的函数来获取 Obj 的值,而 Obj 只是一个指针的包装器,所以本质上他不能通过值传递指针!他可以在原始版本中做到这一点。(通过引用传递仍然不合格:他仍在传递指向指针的指针)

如果您使用移动语义,客户端可以按值获取 Obj。但是,如果您希望复制对象,则让它被复制。使用我的第一个示例,并在堆栈上创建对象,当需要按值传递时,只需按值传递即可。它包含任何复杂的资源(例如指向已分配内存的指针),然后确保编写适当的复制构造函数和赋值运算符,然后您可以创建对象(在堆栈上,或任何您需要的地方),传递它如您所愿,按值、按引用、将其包装在智能指针中或通过移动它(如果您实现了必要的移动构造函数和移动赋值运算符)。

于 2013-03-10T15:22:07.993 回答