How to pass in a class requires a value of the same type in another class?

there is a User class whose method calculate () needs to use a member value in an instance of the external data class as the input source. In the User constructor, you need to pass in different data instances as parameters, and create different User classes according to different data instances.

Let"s first take a look at the two data classes An and B, which are different in structure and hierarchy, but both have the value of Rate

.
struct Rate{
    int value; // 
};

namespace A{
struct Data{
    struct Info{
        int i1,i2;
        Rate rate = {1};
    };
    Data() { info = std::make_shared<Info>();}
    std::shared_ptr<Info> info;
    int aa, ab, ac; //User 
};}

namespace B{
struct Data{
    Rate rate = {2};
    int ba, bb, bc; //User 
};} 

the following is to create a User class, passing in different Data types to build different User instances

User class example 1:

because User only accepts one Data class at a time as a calculation, you can pass pointers to different Data classes in the constructor to determine, and use class instances whose pointers are not null to do the calculation. In calculate (), you need to write different code to access the same rate value in different Data classes.
this code can look disgusting, because different types require different code, and if there are three or four different Data classes, calculate () requires different if branches.

class User1
{
public:
    User1(std::shared_ptr<A::Data> a, std::shared_ptr<B::Data> b){
        a_ = a; b_ = b;
    }

    int calculate(){
        int result = 0;
        if (a_) {
            result = a_->info->rate.value * 10;
        }else if(b_){
            result = b_->rate.value * 10;
        }
        return result;
    }

    std::shared_ptr<A::Data> a_;
    std::shared_ptr<B::Data> b_;
};

int main()
{
    User1 *u1a = new User1(std::make_shared<A::Data>(), nullptr);
    u1a->calculate();
    User1 *u1b = new User1(nullptr, std::make_shared<B::Data>());
    u1b->calculate();
}

User class example 2:

can I pass in different Data as a CPP template?
idea: no matter which kind of Data class, there will be member variables on the rate side, but the level of the call is different, so can the previous type be used as a template parameter?
gave it a try, and the code is as follows, but it still doesn"t feel good enough.

template<class T=B::Data>
class User2
{
public:
    User2(std::shared_ptr<T> data){
        data_ = data;
    }

    int calculate()
    {
      int result = data_->rate.value * 10;
      return result;
    }

    std::shared_ptr<T> data_;
};

int main()
{
    // DataA  A::Data::Info
    auto u2a = new User2<A::Data::Info>(std::make_shared<A::Data::Info>());
    u2a->calculate();
    // DataB  B::Data
    auto u2b = new User2<B::Data>(std::make_shared<B::Data>());
    u2b->calculate();
}

User class example 3:
since the User class needs a rate value, can you pass a function object when constructing the function? call this function object in calculate () to get the rate value of different Data instances.

//useruser
using getRateFunc = std::function<int()>; 
class User3
{
  public:
    User3(getRateFunc getRate)
    {
        getRate_ = getRate;
    }

    int calculate()
    {
        int result = getRate_() * 10;
        return result;
    }

    getRateFunc getRate_;
};

int main()
{
    //User
    auto a = std::make_shared<A::Data>();
    auto u3a = new User3([a](){
        return a->info->rate.value;
    });
    u3a->calculate();

    auto b = std::make_shared<B::Data>();
    auto u3b = new User3([b]() {
        return b->rate.value;
    });
    u3b->calculate();
}

Last question

Note that the Data class cannot inherit an interface because it is a pure data class. Is there a better programming paradigm for
to solve problems in this scenario? Which of the other three examples of
is preferable?


example 3 is a more extensible way to write. Good extensibility means that you don't change any code when adding new Data types. The second template approach requires that the Data type must have a consistent structure, which is too fragile. The problem with
example 3 is that it is not user-friendly, and writing lambda, for every call is too expensive if there are a large number of calls. This can be solved by extracting the helper function (or writing it as a constructor of the User class). Each additional Data type corresponds to the addition of such an auxiliary function (or constructor)
of course, the above is only suitable for this simple logic in the sample code. Actual projects may be much more complex, such as accessing more than a dozen members of a dozen Data types, it is necessary to add an abstraction layer, all User classes inherit from a common interface, and each or several Data types correspond to an implementation. Or you can put the abstract interface inside, and the User class acts as a wrapper class for calling the abstract interface. The advantage of this approach is that the abstraction layer can be modified at any time internally.
what to do can only be analyzed in detail.

Menu