умные указатели — C ++ эквивалентно производительная версия C-подобной реализации

Я изучаю C ++, когда сталкиваюсь с этой ситуацией, где я хочу реализовать эквивалентную эффективную версию на C ++ следующего символического кода на C.

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

Требования следующие:

  • Реализация предоставляется в библиотеке (статической / динамической) и
    заголовок не раскрывает никаких деталей, кроме интерфейса
  • Это должно быть одинаково эффективно

Предоставление интерфейса (подобного COM) с виртуальными функциями не подходит; это решение для включения полиморфизма (более одной реализации, представленной через один и тот же интерфейс), что не соответствует действительности, и, поскольку мне не нужно его значение, я не могу понять, почему я должен оплачивать стоимость вызова функций через 2 косвенных указателя.
Так что моей следующей мыслью была идиома прыщей:

<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);

2

Решение

Почему бы не сохранить оригинал C? Причиной того, что вам не пришлось платить премию за подсчет ссылок в версии C, является то, что версия C полагается на то, что вызывающая сторона будет вести учет количества копий Obj* в использовании.

Пытаясь убедиться, что замена является копируемой и что основной destroy метод вызывается только после уничтожения последней ссылки, вы предъявляете дополнительные требования к оригиналу, поэтому вполне естественно, что правильное решение (которое мне кажется shared_ptr) имеет некоторые ограниченные дополнительные расходы по сравнению с оригиналом.

0

Другие решения

Я предполагаю, что есть причина, почему вам нужен указатель на объект в первую очередь. Потому что самый простой и эффективный подход — просто создать его в стеке:

{
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, если вы используете семантику перемещения. Но если вы хотите, чтобы объект был скопирован, то пусть он будет скопирован. Используйте мой первый пример, создайте объект в стеке и просто передайте его по значению, когда его нужно передать по значению. Он содержит любые сложные ресурсы (например, указатели на выделенную память), затем обязательно напишите соответствующий конструктор копирования и оператор присваивания, а затем вы можете создать объект (в стеке или там, где вам нужно), передать его по вашему желанию, по значению, по ссылке, оборачивая его в умный указатель или перемещая его (если вы реализуете необходимый конструктор перемещения и оператор присваивания перемещения).

-1

По вопросам рекламы [email protected]