Как отключить создание/копирование объектов вне фабричного метода?

У меня есть класс с очень большой полезной нагрузкой, поэтому очень дорого создавать/копировать/перемещать экземпляр этого класса. Поскольку они не изменятся после завершения инициализации приложения, нет необходимости создавать временные объекты этого класса. Мне нужно только кэшировать объекты в контейнере (std::map) и при необходимости предлагать «константную ссылку».

Следует подчеркнуть, что я ищу решение, которое может избежать двойного создания или ненужного копирования объекта перед его добавлением в контейнер (я не думаю, что решение, подобное предложенному @getsoubl, может решить проблему, потому что это не исключает дублирования или ненужного копирования).

Поэтому я хочу расположить метод конструктора в разделе «частный/защищенный» тела класса, чтобы запретить любое создание/копирование/перемещение за пределы «фабричного метода». Ниже приведено мое оригинальное решение:

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 по умолчанию.

Как я могу это сделать?


person Leon    schedule 29.05.2019    source источник
comment
Эта тема кажется дублирующей. Пожалуйста, посмотрите в этом stackoverflow.com/ вопросы/37184137/   -  person getsoubl    schedule 29.05.2019
comment
Я не думаю, что мой вопрос дублируется с этим, потому что решение, которое я ищу, включает не только то, как поместить объект в контейнер, но и то, как избежать ненужного копирования.   -  person Leon    schedule 29.05.2019


Ответы (2)


Если вы хотите заблокировать творение, просто передайте ключ всем, кому разрешен вход!

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;
};
person Deduplicator    schedule 29.05.2019

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

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

Обратите внимание, что конструктор As является приватным и что мы не можем скопировать 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

Прямая демонстрация

person Paul Sanders    schedule 29.05.2019
comment
Это по-прежнему позволяет другим делать свои собственные и не устраняет все копии и перемещения. - person Deduplicator; 29.05.2019
comment
@Deduplicator Я не думаю, что ты очень внимательно читал мой пост. Посторонние не могут создавать свои собственные As, потому что конструктор As является закрытым, а createA скрыт где-то внутри какого-то заводского кода и не отображается ни в одном заголовочном файле. Возможно, мне следовало объявить его static. Я считаю, что я рассмотрел проблемы эффективности в своем посте. Если можно написать дешевый конструктор move для класса A, тогда хорошо, идите по этому пути. Если нет, используйте подход unique_ptr. Конечно, несколько unique_ptr перемещаются, ну и что? Это стоит копейки. Объекты, которыми они владеют, не перемещаются и не копируются. - person Paul Sanders; 29.05.2019
comment
Как я могу получить карту и, следовательно, экземпляр, я могу сделать: auto x = existing_a;. Есть и другие подходы без этой лазейки. - person Deduplicator; 29.05.2019
comment
@Deduplicator Карта скрыта в том же файле, что и createA. Я разъяснил это и удалил оскорбительные конструкторы и операторы присваивания. - person Paul Sanders; 29.05.2019
comment
Хорошо, первый вариант, работающий только с раздачей указателей и самостоятельным созданием, хотя и добавляет косвенность. Честно говоря, я имел в виду второе, и там, кажется, единственный вариант - вообще никому не показывать A. - person Deduplicator; 29.05.2019
comment
@Deduplicator Да, я предпочитаю первый. Я скорее жалею, что добавил второй. Есть ли какие-либо косвенные затраты при преобразовании unique_ptr в const ref (это то, что OP хочет вернуть из поиска)? Если он есть, он в любом случае будет завален поиском ключа на карте. - person Paul Sanders; 29.05.2019
comment
С помощью unique_ptr делаю мой текущий метод в коде продукта, но он мне не нравится. Я придираюсь к тому, как отмена ссылки на смарт-птр будет тратить время, особенно когда поиск будет выполняться очень часто. - person Leon; 29.05.2019
comment
Лично я думаю, что стоимость тривиальна, но вы должны признать, что решения Deduplicator очень изящны. - person Paul Sanders; 29.05.2019