2015-09-24 2 views
11

Я работаю с унаследованным C API, при котором приобретение какого-то ресурса дорого и освобождает этот ресурс абсолютно критично. Я использую C++ 14, и я хочу создать класс для управления этими ресурсами. Вот основной скелет вещи ...C++ Move Semantics - обертывание API Legacy C

class Thing 
{ 
private: 
    void* _legacy; 

public: 
    void Operation1(...); 
    int Operation2(...); 
    string Operation3(...); 

private: 
    Thing(void* legacy) : 
     _legacy(legacy) 
    { 
    } 
}; 

Это не единственный синглетный узор. Ничто не является статичным, и может быть много экземпляров Thing, которые управляют собственными ресурсами. Кроме того, это не просто умный указатель. Обернутый указатель _legacy является закрытым, и все операции отображаются через некоторые функции публичных экземпляров, которые скрывают унаследованный API от потребителей.

Конструктор является закрытым, поскольку экземпляры Thing будут возвращены с статического завода или named-constructor, который фактически приобретет ресурс. Вот дешевая имитация этой фабрики, используя malloc() в качестве места держателя для кода, который будет ссылаться на устаревшую API ...

public: 
    static Thing Acquire() 
    { 
     // Do many things to acquire the thing via the legacy API 
     void* legacy = malloc(16); 

     // Return a constructed thing 
     return Thing(legacy); 
    } 

Вот деструктор, который отвечает за освобождение унаследованного ресурса, опять же, free() просто заполнитель ...

~Thing() noexcept 
    { 
     if (nullptr != _legacy) 
     { 
      // Do many things to free the thing via the legacy API 
      // (BUT do not throw any exceptions!) 
      free(_legacy); 
      _legacy = nullptr; 
     } 
    } 

Теперь я хочу, чтобы убедиться, что именно один наследство ресурса управляется ровно одним экземпляром Thing. Я не хотел, чтобы пользователи класса Thing передавали экземпляры по желанию - они должны либо принадлежать локально к классу или функции, либо напрямую, либо через unique_ptr, либо обернуты shared_ptr, которые могут быть переданы. С этой целью я удалил оператор присваивания и скопировал конструкторы ...Либо мне пришлось изменить свой заводский метод, чтобы вернуть unique_ptr<Thing> или shared_ptr<Thing>, или мне пришлось реализовать семантику перемещения. Я не хочу, чтобы диктовать шаблон, под которым Thing следует использовать таким образом, я решил добавить Move-конструктор и перемещения присваивания-оператор следующим образом ...

Thing(Thing&& old) noexcept : _legacy(old._legacy) 
    { 
     // Reset the old thing's state to reflect the move 
     old._legacy = nullptr; 
    } 

    Thing& operator= (Thing&& old) noexcept 
    { 
     if (&old != this) 
     { 
      swap(_legacy, old._legacy); 
     } 
     return (*this); 
    } 

С этим все сделано, я мог бы использовать Thing как местные и переместить его о ...

Thing one = Thing::Acquire(); 
    Thing two = move(one); 

Я не мог разорвать шаблон, пытаясь совершить самостоятельно задание:

Thing one = Thing::Acquire(); 
    one = one;      // Build error! 

I Кл d также сделать unique_ptr на один ...

auto three = make_unique<Thing>(Thing::Acquire()); 

Или shared_ptr ...

auto three = make_shared<Thing>(Thing::Acquire()); 

Все работало, как я ожидал, и мой деструктор побежал точно в нужный момент во всех моих тестах. На самом деле, единственным раздражением было то, что make_unique и make_shared как фактически вызывают движение-конструктор - он не был оптимизирован, как я и надеялся.

Первый вопрос: ли я реализовал ход -constructor и перемещения присваивания-оператора правильно? (Они довольно новы для меня, и это будет первый раз, когда я использовал его в гневе.)

Второй вопрос: Прокомментируйте этот шаблон! Это хороший способ обернуть устаревшие ресурсы в классе C++ 14?

И наконец: Должен ли я что-либо изменить, чтобы код был лучше, быстрее, проще или читабельнее?

+0

Вы всегда можете использовать [статический одноэлементный шаблон] (http://stackoverflow.com/questions/1008019/c-singleton-design-pattern), чтобы избежать использования интеллектуальных указателей. – NathanOliver

+0

Это не синглтон - только псевдо-синглтон. Там может быть несколько вещей, все приобретаются отдельно, но управляются экземпляром «Thing» каждый. – Xharlie

+0

Кажется, вы изобретаете умный указатель, а затем сохраняете его в другом умном оверте. Я бы просто написал пользовательский делетер и напрямую использовал 'std :: unique_ptr' или' std :: shared_ptr'. – Galik

ответ

2
struct free_thing{ 
    void operator()(void* p)const{ 
    // stuff 
    free(p); 
    } 
}; 
using upthing=std::unique_ptr<void,free_thing>; 

upthing make_thing(stuff){ 
    void*retval; 
    // code 
    return upthing(retval); 
} 

Сохранять upthing в вашем Thing в _legacy. Используйте dtor по умолчанию, переместите ctor, переместите назначение для Thing (=default).

Код ошибки находится в free_thing.

Ваш ctor создает вверх.

Теперь пользователи могут обрабатывать ваш Thing как тип значения только для перемещения.

Не пишите своих менеджеров указателей, если вам действительно не нужно. Уникальные и общие делают для вас много: если вы пишете свой собственный умный указатель, используйте их как внутренние кишки.

+0

Мне нравится. Что произойдет, если код destroy вызывает исключение? Разрешено ли исключать исключения? – Xharlie

6

Вы должны обернуть свой Thing умным указателем, тогда вам не нужно будет беспокоиться о семантике копирования и перемещении.

class Thing 
{ 
private: 
    void* _legacy; 

public: 
    void Operation1(...); 
    int Operation2(...); 
    string Operation3(...); 

    Thing(const Thing&) = delete; 
    Thing(Thing&&) = delete; 
    Thing& operator=(const Thing&) = delete; 
    Thing& operator=(Thing&&) = delete; 

    static std::shared_ptr<Thing> acquire() { 
     return std::make_shared<Thing>(); 
    } 

private: 
    Thing() : _legacy(malloc(16)) { 
     // ... 
    } 

    ~Thing() { 
     free(_legacy); 
    } 
}; 

Кроме того, вы можете сделать это с unique_ptr:

std::unique_ptr<Thing> acquire() { 
    return std::make_unique<Thing>(); 
} 

Вы, кажется, подразумевает, что вы хотите иметь только один экземпляр этой вещи, хотя даже в вашем решении вы не пытались сделать ничего подобного. Для этого вам нужны статические переменные. Хотя помните, что в этом случае ваш ресурс будет освобожден только после выключения функции main(). Например:

static std::shared_ptr<Thing> acquire() { 
    static std::shared_ptr<Thing> instance; 
    if (!instance) { 
     instance = std::make_shared<Thing>(); 
    } 
    return instance; 
} 

или версия unique_ptr:

static Thing& acquire() { 
    static std::unique_ptr<Thing> instance; 
    if (!instance) { 
     instance = std::make_unique<Thing>(); 
    } 
    return *instance; 
} 

Или, вы можете использовать weak_ptr приобрести один экземпляр в рамках всей программы, которая освобождается, когда никто не использует его. В этом случае вы не сможете использовать unique_ptr для этой цели. Эта версия воссоздает объект, если он был освобожден, а затем снова нужен.

static std::shared_ptr<Thing> acquire() { 
    static std::weak_ptr<Thing> instance; 
    if (instance.expired()) { 
     instance = std::make_shared<Thing>(); 
    } 
    return instance.lock(); 
} 
+3

@Xharlie Но принцип, согласно которому вы можете предоставить пользовательский удаляющий элемент интеллектуальному указателю, по-прежнему действителен. – Galik

+0

Достаточно честный. Отмечается пользовательский делектор. Но, в конечном счете, класс будет иметь на нем другие функции, которые не просто выделение и освобождение. – Xharlie

+0

@ Xharlie Затем вы можете создать класс 'Thing' с любыми операциями, которые делают' malloc() 'вещь в своем конструкторе и' free() 'в деструкторе, а затем помещают его в ваши' shared_ptr' или 'unique_ptr' как я описал. Тогда вам не понадобятся пользовательские удалители, и вы даже можете использовать 'make_shared' или' make_unique'. – petersohn

Смежные вопросы