У меня есть класс с очень большой полезной нагрузкой, поэтому очень дорого создавать/копировать/перемещать экземпляр этого класса.
Поскольку они не изменятся после завершения инициализации приложения, нет необходимости создавать временные объекты этого класса. Мне нужно только кэшировать объекты в контейнере (std::map
) и при необходимости предлагать «константную ссылку».
Следует подчеркнуть, что я ищу решение, которое может избежать двойного создания или ненужного копирования объекта перед его добавлением в контейнер (я не думаю, что решение, подобное предложенному @getsoubl, может решить проблему, потому что это не исключает дублирования или ненужного копирования).
Поэтому я хочу расположить метод конструктора в разделе "частный/защищенный" тела класса, чтобы запретить любое создание/копирование/перемещение за пределы "Factory-Method". Ниже приведено мое оригинальное решение:
class MyClass {
public:
// methods of the class
static const MyClass & findObject( int iKey ) {
auto pair = mapObjects.try_emplace( iKey, iKey );
if ( pair.second )
cout << "New object has been created" << endl;
return pair.first->second;
};
// deleted
MyClass() = delete;
MyClass( MyClass & ) = delete;
MyClass( MyClass && ) = delete;
MyClass( const MyClass & ) = delete;
MyClass( const MyClass && ) = delete;
MyClass & operator=( MyClass & ) = delete;
MyClass & operator=( MyClass && ) = delete;
MyClass & operator=( const MyClass & ) = delete;
MyClass & operator=( const MyClass && ) = delete;
private:
// vars of the class
static map<int, MyClass> mapObjects;
// vars of instance
string some_heavy_payload;
// methods of instance
MyClass( int iKey ) :
some_heavy_payload( std::to_string( iKey ) ) {};
};
map<int, MyClass> MyClass::mapObjects;
int main() {
const MyClass & obj = MyClass::findObject( 1 );
return EXIT_SUCCESS;
};
Но я столкнулся с противоречием, что «std::try-emplace» НЕ может также вызывать конструктор MyClass. Компилятор сообщает: «ошибка: ‘MyClass::MyClass(int)’ является закрытым в этом контексте».
Итак, я попробовал решение 2:
class MyClass {
public:
// methods of the class
static const MyClass & findObject( int iKey ) {
if ( mapObjects.find( iKey ) == mapObjects.cend() )
mapObjects[iKey] = MyClass( iKey );
return mapObjects[iKey];
};
// deleted
MyClass() = delete;
MyClass( MyClass & ) = delete;
MyClass( MyClass && ) = delete;
MyClass( const MyClass & ) = delete;
MyClass( const MyClass && ) = delete;
MyClass & operator=( MyClass & ) = delete;
MyClass & operator=( const MyClass & ) = delete;
MyClass & operator=( const MyClass && ) = delete;
private:
// vars of the class
static map<int, MyClass> mapObjects;
// vars of instance
string some_heavy_payload;
// methods of instance
MyClass( int iKey ) {
some_heavy_payload = std::to_string( iKey );
};
MyClass & operator=( MyClass && src ) {
some_heavy_payload = std::move( src.some_heavy_payload );
return *this;
};
};
map<int, MyClass> MyClass::mapObjects;
int main() {
const MyClass & obj = MyClass::findObject( 1 );
return EXIT_SUCCESS;
};
На этот раз я получил ошибку: «использование удаленной функции ‘MyClass::MyClass()’». Я предполагаю, что это результат оператора "[]" std::map, потому что он пытается вызвать конструктор MyClass по умолчанию.
Как я могу это сделать?
Я не думаю, что мой вопрос дублируется этим, потому что решение, которое я ищу, включает не только «как поместить объект в контейнер», но и «как избежать ненужного копирования».
Вы можете добиться этого, используя карту указателей (которые дешево копировать или перемещать на карту), а не карту объектов, и если вы используете карту интеллектуальных указателей, вы все равно можете использовать карту для управления временем жизни объекты внутри него.
Вот некоторый код для проверки концепции, который должен позволить вам развить эту идею. На практике createA
и карта будут скрыты внутри какой-то фабричной функции для заполнения карты.
Обратите внимание, что конструктор A
является приватным и что мы не можем скопировать unique_ptr
, только переместить его. Кроме того, make_unique
запрещен, потому что у A
есть частный конструктор, но это не имеет большого значения с точки зрения затрат. Копирование указателей дешево.
#include <iostream>
#include <map>
#include <memory>
class A
{
A () { std::cout << "Create A" << '\n'; }
A (const A &) = delete;
A &operator= (const A&) = delete;
A (A &&) = delete;
A &operator= (const A&&) = delete;
public:
~A () { std::cout << "Destroy A" << '\n'; }
friend void createA (int key);
};
static std::map <int, std::unique_ptr <A>> objects;
void createA (int key)
{
std::unique_ptr <A> a (new A);
objects.insert (std::pair <int, std::unique_ptr <A>> (key, std::move (a)));
}
int main ()
{
createA (1);
createA (2);
}
Вывод (показывает управление временем жизни объекта по карте):
Create A
Create A
Destroy A
Destroy A
Альтернативно, напишите эффективный конструктор перемещения (что обычно несложно) и переместите свои объекты на карту, а не копируйте их, например:
#include <iostream>
#include <map>
#include <memory>
class A
{
A () { std::cout << "Create A" << '\n'; }
A (const A &) = delete;
A &operator= (const A&) = delete;
A &operator= (const A&&) = delete;
public:
A (const A &&) { std::cout << "Move A" << '\n'; }
~A () { std::cout << "Destroy A" << '\n'; }
friend void createA (int key);
};
static std::map <int, A> objects;
void createA (int key)
{
A a;
objects.insert (std::pair <int, A> (key, std::move (a)));
}
int main ()
{
createA (1);
createA (2);
}
Выход:
Create A
Move A
Move A
Destroy A
Destroy A
Create A
Move A
Move A
Destroy A
Destroy A
Destroy A
Destroy A
Это по-прежнему позволяет другим делать свои собственные и не устраняет все копии и перемещения.
@Deduplicator Я не думаю, что ты очень внимательно читал мой пост. Посторонние не могут создавать свои собственные A
, потому что конструктор A
является приватным, а createA
спрятан где-то внутри какого-то заводского кода и не отображается ни в одном заголовочном файле. Возможно, я должен был объявить об этом static
. Я считаю, что я рассмотрел проблемы эффективности в своем посте. Если можно написать дешёвый move
конструктор для класса A
, тогда хорошо, идите по этому пути. Если нет, используйте подход unique_ptr
. Конечно, несколько unique_ptrs перемещаются, ну и что? Это стоит копейки. Объекты, которыми они владеют, не перемещаются и не копируются.
Как я могу получить карту и, следовательно, инстанс, я могу сделать: auto x = existing_a;
. Есть и другие подходы без этой лазейки.
@Deduplicator Карта скрыта в том же файле, что и createA
. Я разъяснил это и удалил оскорбительные конструкторы и операторы присваивания.
Хорошо, первый вариант, работающий только с раздачей указателей и самостоятельным созданием, хотя и добавляет косвенность. Честно говоря, я имел в виду второе, и там, похоже, единственный вариант — вообще никому не показывать A
.
@Deduplicator Да, я предпочитаю первый. Я скорее жалею, что добавил второй. Есть ли какие-либо косвенные затраты при преобразовании unique_ptr
в const ref
(это то, что ОП хочет вернуть из поиска)? Если он есть, он в любом случае будет завален поиском ключа на карте.
С помощью unique_ptr делаю мой текущий метод в коде продукта, но он мне не нравится. Я придираюсь к тому, как отмена ссылки на смарт-птр будет тратить время, особенно когда поиск будет выполняться очень часто.
Лично я думаю, что стоимость тривиальна, но вы должны признать, что решения Deduplicator очень изящны.
Если вы хотите заблокировать творение, просто передайте ключ всем, кому разрешен вход!
class MyClass {
class Key {
Key() = default;
friend class MyClass;
};
MyClass(MyClass const&) = delete;
MyClass& operator=(MyClass const&) = delete;
static map<int, MyClass> mapObjects;
public:
static MyClass const& findObject(int iKey) {
auto [iter, created] = mapObjects.try_emplace(iKey, Key(), iKey );
if (created)
std::cout << "New object has been created\n";
return iter->second;
};
MyClass(Key, int iKey)
: some_heavy_payload(std::to_string(iKey))
{}
private:
string some_heavy_payload;
};
Похоже, эта тема дублируется Пожалуйста, посмотрите в этом stackoverflow.com/questions/37184137/…