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

У меня есть класс с очень большой полезной нагрузкой, поэтому очень дорого создавать/копировать/перемещать экземпляр этого класса. Поскольку они не изменятся после завершения инициализации приложения, нет необходимости создавать временные объекты этого класса. Мне нужно только кэшировать объекты в контейнере (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 по умолчанию.

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

Похоже, эта тема дублируется Пожалуйста, посмотрите в этом stackoverflow.com/questions/37184137/…

getsoubl 29.05.2019 11:53

Я не думаю, что мой вопрос дублируется этим, потому что решение, которое я ищу, включает не только «как поместить объект в контейнер», но и «как избежать ненужного копирования».

Leon 29.05.2019 16:11
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
130
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Вот некоторый код для проверки концепции, который должен позволить вам развить эту идею. На практике 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 29.05.2019 16:47

@Deduplicator Я не думаю, что ты очень внимательно читал мой пост. Посторонние не могут создавать свои собственные A, потому что конструктор A является приватным, а createA спрятан где-то внутри какого-то заводского кода и не отображается ни в одном заголовочном файле. Возможно, я должен был объявить об этом static. Я считаю, что я рассмотрел проблемы эффективности в своем посте. Если можно написать дешёвый move конструктор для класса A, тогда хорошо, идите по этому пути. Если нет, используйте подход unique_ptr. Конечно, несколько unique_ptrs перемещаются, ну и что? Это стоит копейки. Объекты, которыми они владеют, не перемещаются и не копируются.

Paul Sanders 29.05.2019 16:55

Как я могу получить карту и, следовательно, инстанс, я могу сделать: auto x = existing_a;. Есть и другие подходы без этой лазейки.

Deduplicator 29.05.2019 17:03

@Deduplicator Карта скрыта в том же файле, что и createA. Я разъяснил это и удалил оскорбительные конструкторы и операторы присваивания.

Paul Sanders 29.05.2019 17:27

Хорошо, первый вариант, работающий только с раздачей указателей и самостоятельным созданием, хотя и добавляет косвенность. Честно говоря, я имел в виду второе, и там, похоже, единственный вариант — вообще никому не показывать A.

Deduplicator 29.05.2019 17:34

@Deduplicator Да, я предпочитаю первый. Я скорее жалею, что добавил второй. Есть ли какие-либо косвенные затраты при преобразовании unique_ptr в const ref (это то, что ОП хочет вернуть из поиска)? Если он есть, он в любом случае будет завален поиском ключа на карте.

Paul Sanders 29.05.2019 17:41

С помощью unique_ptr делаю мой текущий метод в коде продукта, но он мне не нравится. Я придираюсь к тому, как отмена ссылки на смарт-птр будет тратить время, особенно когда поиск будет выполняться очень часто.

Leon 29.05.2019 18:39

Лично я думаю, что стоимость тривиальна, но вы должны признать, что решения Deduplicator очень изящны.

Paul Sanders 29.05.2019 18:52
Ответ принят как подходящий

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

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;
};

Другие вопросы по теме