Pull to refresh

Comments 9

Не хочу прослыть токсиком, но ... у Вас просто C++ wrapper, никакого отношения отношения к C++17 и даже C++11. Заголовок вводит в заблуждение.

"Доставили" следующие фрагменты кода:

void AES_t::clear() { *this = AES_t(); }

// Оператор присвоения   
inline ByteArray& operator=(ByteArray other) {
    this->~ByteArray();     
    return *new(this) ByteArray(std::move(other));
}

Надеюсь никогда такого не увидеть в production :D

Из-за наличия инициализации в условных конструкциях:

if(uint64_t reuired_length = f_length + s_length; reuired_length < ciphertext.length())
    ciphertext.resize(f_length + s_length);
  else if(reuired_length > ciphertext.length())
    throw std::runtime_error("Predicted ciphertext size lower then actual!");

Которая появилась в C++ 17-й версии, есть требования к версии C++ при компиляции кода, из-за этого и появилась версия C++ в названии, поскольку в некоторых версиях MinGW попрежнему явно приходится задавать версию при компиляции, если требуется поддержка версии C++ выше 14-й, я решил на это указать.

Хотелось бы уточнить, что именно не устраивает в участках кода, что вы отметили:

// Присвоить к текущему экземпляру класса AES_t
// экземпляр AES_t сконструированный поумолчанию
void AES_t::clear() { *this = AES_t(); }
// Использование логики определённой в RAII для копирования объекта
// при присвоении, по сути вызвать деструктор и конструктор через placement-new от
// скопированного/перемещённого из вне функции экземпляра данного
// класса
inline ByteArray& operator=(ByteArray other) {
    this->~ByteArray();     
    return *new(this) ByteArray(std::move(other));
}

Вполне понятные и рабочие варианты сделать то что от данных методов требуется.

По поводу того, что это просто обёртка для работы с функциями OpenSSL - вы правы, стоило это указать в названии статьи, чтобы небыло заблуждений по поводу содержания. Уже исправил.

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

  2. Зачем выделять объект через new? тем более мувить в него переданный по значению (то есть скопированный до этого в аргумент функции) объект?? А тем временем это не похоже не на копирующий, ни на перемещающий операторы присваивания. Обычно либо копируются мемберы или перемещаются они же, а тут что-то очень странное происходит, а потом будете говорить, что на С++ писать не возможно))
    Ручной вызов деструктора для this? Вы разрушаете объект во время действия его метода? выглядит странно. Деструктор автоматов выховется для уничтожаемого объекта.
    И, наконец, что будет с выделенной памятью если, например 3 раза подряд вызвать этот оператор? ну для трех объектов память мы выделим, а кто будет освобождать?

По поводу 3-го. Ничего не будет, так как используется placement new. https://en.cppreference.com/w/cpp/language/new Он не выделяет память, а конструирует объект в указанном, заранее выделенном месте.

а вот то, что это placement new - я упустил, да

Да, выглядит странно, но тем не менее это рабочий способ описать лаконично некоторые методы. Сам по себе деструктор не вычищает память из под экземпляра класса это делает только delete. По факту оператор delete - это комбинация вызовов деструкторов и последующий вызов free. Деструктор служит лишь для деинициализации экземпляра класса. Сделано это так чтобы можно было как переместить объект, так и скопировать:

ByteArray a(15), b(10), c(12);
b = a; // Скопировали a в b
// Вызванный деструктор b очистит выделенную память в указателе b.byte_array
c = std::move(a); // Переместили a в c
// Вызванный деструктор c очистит выделенную память в указателе c.byte_array

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

ByteArray& ByteArray::operator=(ByteArray other) {
	if(byte_array) delete byte_array;
  _length = other._length;
  byte_array = other.byte_array;
  other.byte_array = nullptr;
  return *this;
}

Но если разницы особой нет, особого смысла это не имеет. Иногда случается, что в классе множество разных поинтеров, память из которых нужно вычищать. Да, можно вынести код очистки в отдельный приватный метод например в void clear() и за тем уже вызывать их в деструкторах и операторах присвоения. Можно конечно ещё использовать std::unique_ptr.

Что касательно использования placement-new - то он нужен лишь за тем чтобы вызвать конструктор у куска не инициализированной памяти, тоже вполне стандартная функция не взывающая никаких аллокаций/реаллокаций памяти кроме тех что прописаны в конструктор непосредственно.

Например им удобно пользоваться, если в некотором куске не инициализированной памяти надо сконструировать два разных экземпляра:

uint8_t* a = (uint8_t*)malloc(sizeof(uint32_t) + sizeof(long double));
new(a) uint32_t(255);
new(a+sizeof(uint32_t)) long double(15.256);

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

Вы не работали с embedded, где порой вообще не бывает реализации new и malloc, так как не существует "кучи" как таковой, это, конечно про глубокий bare-metal embedded, но всё же.

Код «попахивает», и никакой необходимости в таком подходе здесь нет.

никакой необходимости в таком подходе здесь нет.

это отдельный вопрос, я только объяснил трюк с ручным вызовом деструктора и placement new для конструирования нового объекта. Для обычных приложений с полноценной ОС он нужен крайне редко, для встройки применяется очень часто.

Хотя согласен, порой разработчики встройки без операционок такое пишут, что страшно становиться. Типичный пример - имитация шаблонов для чистого Си на макросах.

Sign up to leave a comment.

Articles