c++constructorc++17

C++ propagate constructor update to client codebase


So I am trying a add new feature to a legacy codebase. And want to do it with minimal impact to client codes.

I have a class with a default constructor and some non-default constructor too.

struct A {
   A() {}
   A(int x) {}
   A& operator+=(const A& other);
   std::array<int, 4> data;
};

previously class A had a static array of fixed size as a member. Now, with new requirement, the size of array is no longer fixed and has to be a dynamic array. The information of what should be the size, is stored in class B.
Now, with the new requirement, I have a add a required argument to all the constructor of class A.
e.g. class A will now look like below:

struct A {
   A(B *b) { size = b->getSize(); data = new int[size];}
   A(B *b, int x) { size = b->getSize(); data = new int[size]; }
   int *data;
};

The problem is, in the current client code, class A is instantiated at multiple places. (probably > 10,000 instances) and updating all places is not feasible.

Is there any C++ trick / pattern that can help me to propagate this change to client code seamlessly (or with minimal changes) I am trying to think of a solution but couldn't think of any. Any pointers would be greatly appreciated.


Solution

  • You can change b to be the last parameter of your contructors and give it a default value of nullptr.
    Then if b is nullptr use the fixed size that was previously used.

    This way the client can gradually upgrade to the enhanced API without the need to change the code all at once.

    struct A {
    
        static constexpr size_t DEFAULT_SIZE = 4; // Use the previous fixed size
    
        A(B *b = nullptr)        { size = b ? b->getSize() : DEFAULT_SIZE; data = new int[size]; }
        A(int x, B *b = nullptr) { size = b ? b->getSize() : DEFAULT_SIZE; data = new int[size]; }
    
        // ...
    
        int *  data;
        size_t size;
    };
    

    A side note:
    Instead of using a raw C array, it is recommended to use std::vector.


    Update:

    You commented below that you would like b->getSize() to be the only source of size for the array.
    Assuming all your B instances return the same size, you could create a B singleton, and use it as a default b instead of the nullptr:

    #include <assert.h>
    
    B* GetBSingleton()
    {
        static B b;
        return &b;
    }
    
    struct A {
    
        A(B* b = GetBSingleton())        { assert(b); size = b->getSize(); data = new int[size]; }
        A(int x, B* b = GetBSingleton()) { assert(b); size = b->getSize(); data = new int[size]; }
    
        // ...
    
        int* data;
        size_t size;
    };
    
    

    Note: the implementation of GetBSingleton() above is following the idea from Meyers' Singleton (using a static variable in the singleton getter).

    Another option combines the 2 solutions:
    Keep nullptr as a default value for b, but in case b is indeed a nullptr get the size from GetBSingleton()->getSize() instead of b->getSize().
    The advantage here is that the header file is kept cleaner and the definition of GetBSingleton() can be pushed to the implementation file (.cpp).