Можно ли обойти решение unique_ptr <MyType>, чтобы не нуждаться в определении деструктора MyType при сохранении только nullptr? - PullRequest
3 голосов
/ 07 июня 2019

Мне нужно использовать компилятор VS2012 и иметь:

virtual std::unique_ptr<MyType> pass_through(std::unique_ptr<MyType> instance) override { return std::unique_ptr<MyType>(nullptr); };

Это определение существует только для проекта в качестве заглушки, и без деструктора MyType я получаю следующую ошибку:

ошибка LNK2001: неразрешенный внешний символ "public: __thiscall MyType :: ~ MyType (void)" (?? 1MyType @@ QAE @ XZ)

Итак, я создал определение:

MyType::~MyType() {}

и в этом проблема, я не хочу иметь запутанное определение только для того, чтобы вышеприведенная функция проходила сборку ... поэтому есть способ не нуждаться в указании определения деструктора и при этом иметь действительную реализацию этого метода pass_through

Может быть, я могу каким-то образом изменить сигнатуру метода, или это логично сделать в основном то же самое в первичной реализации, он делает что-то вроде:

std::unique_ptr<MyType> pass_through(std::unique_ptr<MyType> instance)
{
    if (!instance) {
        instance= std::unique_ptr<MyType>(new MyType(/*arguments*/));
    }
    instance->something();
    return instance;
}

Между прочим, я вижу, что аналогичные вопросы недооценены / закрыты, но, тем не менее, в предложениях я не вижу ни одного соответствующего ответа, и я также раньше пользовался Google, но до сих пор не нажал => может быть, как-то продвинуть соответствующий вопрос с хорошим ответом, если есть есть кто-нибудь?

Ответы [ 3 ]

1 голос
/ 07 июня 2019

Я думаю, что есть несколько аспектов вопроса.Во-первых, это понимание того, что делает unique_ptr:

Это обернет ваш класс в RAII-стиле, удерживая указатель на ваш класс, и освободит его при разрушении или повторном назначении.Чтобы убедиться, что мы находимся на той же странице, давайте рассмотрим примерную реализацию unique_ptr (взято из this question):

template<typename T>
class unique_ptr {
private:
    T* _ptr;
public:
    unique_ptr(T& t) {
       _ptr = &t;
    }
    unique_ptr(unique_ptr<T>&& uptr) {
       _ptr = std::move(uptr._ptr);
       uptr._ptr = nullptr;
    }
    ~unique_ptr() {
       delete _ptr;
    }
    unique_ptr<T>& operator=(unique_ptr<T>&& uptr) {
       if (this == uptr) return *this;
       _ptr = std::move(uptr._ptr);
       uptr._ptr = nullptr;
       return *this;
    }

    unique_ptr(const unique_ptr<T>& uptr) = delete;
    unique_ptr<T>& operator=(const unique_ptr<T>& uptr) = delete;
};

Как вы можете видеть, деструктор с уникальным указателем вызывает deleteдля реального объекта в функции ~ unique_ptr ().

Теперь давайте взглянем на стандарт:

3.7.4 Продолжительность динамического хранения [basic.stc.dynamic] 1 Объектыможет создаваться динамически во время выполнения программы (1.9) с использованием выражений new (5.3.4) и уничтожаться с помощью выражений delete (5.3.5).Реализация C ++ обеспечивает доступ к динамическому хранилищу и управление им с помощью оператора глобальных функций выделения new и оператора new [], а также оператора удаления глобальных функций удаления и оператора удаления [].

Также:

Если значение операнда выражения удаления не является значением нулевого указателя, выражение удаления вызовет деструктор (если он есть) для объекта или элементов удаляемого массива.В случае массива элементы будут уничтожены в порядке убывания адреса (то есть в обратном порядке завершения их конструктора; см. 12.6.2).

Теперь, учитывая, что этоповедение, предписанное стандартом, вам нужно объявить и определить деструктор.

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

Итак, в заключение вам нужно либо объявить и определить деструктор самостоятельно, либо использовать автоматически созданный по умолчанию (даже не объявлять его), либо объявить его пометкой по умолчанию.(скажем, компилятор, который вы его определили, но он должен иметь реализацию по умолчанию).

0 голосов
/ 07 июня 2019

Я могу вспомнить только один сценарий, в котором может возникнуть ваша проблема. И тогда вы объявляете деструктор, но не определяете его:

struct X { ~X(); };

int main() {
  std::unique_ptr<X> p(new X); 
}

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

struct X { };

Теперь все отлично работает.


Обратите внимание, что если деструктор был удален (либо вручную, либо компилятором, например, производным от неразрушаемой базы), то вы получите ошибку время компиляции , а не время соединения один:

struct X { ~X() = delete; };
0 голосов
/ 07 июня 2019

Вам не нужно определение в том же модуле компиляции.Я думаю, что вы ссылаетесь неправильно.(Файл .cpp, который содержит void MyType::something() { /* ... */ }, также должен иметь MyType::~MyType() { /* ... */ }, поэтому, если вы используете (MyType*)->something, вы сможете использовать деструктор).

Если это действительно то, что вы хотите сделать(Скомпилируйте небольшой раздел вашей программы, не связывая модуль с деструктором, по любой причине), это было бы невозможно при использовании значения по умолчанию std::unique_ptr<T>, для которого необходимо вызвать delete (T*);, что в конечном итоге потребует наличия деструктора.

Вы можете использовать пользовательское средство удаления, которое в особом случае можно использовать как средство удаления "ничего не делать".Это означает, что вам не нужен символ MyType::~MyType:

#include <memory>

class MyType {
public:
    void something();

    ~MyType();
};

// `MyUniquePtr` calls a stored function pointer to delete it's value
using MyUniquePtr = std::unique_ptr<MyType, void(*)(const MyType*) noexcept>;

void MyTypeDeleter(const MyType* p) noexcept;

// This is compatible with `MyUniquePtr`'s deleter, and does nothing when called
void MyTypeNullDeleter(const MyType* p) noexcept {
    // For extra safety add this check
    if (p != nullptr) std::terminate();

    // Otherwise do nothing
    // (Don't need to delete a nullptr, don't need a destructor symbol)
}

MyUniquePtr pass_through(MyUniquePtr instance) {
    return MyUniquePtr(nullptr, MyTypeNullDeleter);
};
// In a seperate unit where the destructor is defined, you still
// have to use the `MyUniquePtr` type for the virtual functions,
// but need a deleter which actually deletes the pointer.
// That's what `MyTypeDeleter` is.

MyType::~MyType() { }

void MyTypeDeleter(const MyType* p) noexcept {
    delete p;
}

MyUniquePtr pass_through2(MyUniquePtr instance) {
    if (!instance) {
        instance = MyUniquePtr(new MyType(/*arguments*/), MyTypeDeleter);
    }
    instance->something();
    return instance;
}

Но легко случайно удалить «ничего не делать», назначенный ненулевому указателю.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...