Search code examples
c++factoryfactory-patternmove-constructoremplace

How to disable creating/copying obj outside a Factory-Method?


I have a class, that has very heavy payload, so that it is very expensive to create/copy/move an instance of this class. Since they will not change after the app has finished initialization, there is no need to create temporary objects of this class anywere. I only need to cache objects in a container(std::map), and offer a "const reference" when needed.

The thing that must be emphasized is that I'm seeking a solution which can avoid double-create or unnecessary copy a object before add it into the container(I don't think a solution likes one @getsoubl proposed can resolve the problem, because it does not eliminate doulbe-creating or unnecessary copying).

So I want to arrange the constructor method into "private/protected" section of the class body, so as to forbid any of creating/copying/moving outside of the "Factory-Method". Following is my original solution:

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

But I'm put into a contradiction that the "std::try-emplace" can NOT also call the constructor of MyClass. The compiler reports: "error: ‘MyClass::MyClass(int)’ is private within this context".

So I tried the solution 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;
};

This time I got a error: "use of deleted function ‘MyClass::MyClass()’". I guess that is resulted by "[]" operator of std::map, because it tries to call the default constructor of MyClass.

How can I get it done?


Solution

  • If you want to lock creation down, simply pass a key to everyone allowed in!

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