0

Edit: My question might just be asking how to downcast a unique_ptr<base> to unique_ptr<derived> (which is already answered), but I am not 100% sure what I am asking

I have an Abstract Base Class Base

class Base{
  public:
    Base();
    struct pStruct{};
    virtual pStruct pFunc(std::vector<double> data) = 0;
  protected:
    CustomType dataValue;
};

and two derived classes Derived1 and Derived2 that implement Base

class Derived1 : public Base {
  public:
    struct pStructD1 : Base::pStruct {
      CustomType data1;
      std::vector<double> data2;
    };
    Derived1(uint32_t foo1, std::vector<double> foo2, ...);
    virtual pStruct pFunc(std::vector<double> data) override;
  private:
    uint32_t bar1{0};
};
class Derived2 : public Base {
  public:
    struct pStructD2 : Base::pStruct {
      int32_t data3;
      std::vector<double> data4;
      double data5
    };
    Derived2(std::vector<double> foo1, std::vector<double> foo2, ...);
    virtual pStruct pFunc(std::vector<double> data) override;
  private:
    std::vector<double> bar2;
};

When calling class method pFunc(std::vector<double> data), each derived class will return different types, and amounts of values. I tried making this work with a covariant return type, so Derived1::predict(data).key1 might be a matrix, and .key2 might be something else, and so on. Derived2::predict(data).key1 might be the only key, and it could be a boolean. Each derived class defines their own ::predict() return fields, because they vary significantly.

The issue is, I construct these derived classes with a factory, that reads some of the input (construction is via ifstream), and figures out what derived class it should be, and then calls the corresponding factory.

class BaseFactory {
  public:
    static std::unique_ptr<Base> createObj(std::ifstream & file){
      file.read((char *) specificTypeString, 2);//This isn't actually the code, just assume this part works
      if(specificTypeString == "D2"){
        return D2BaseFactory::createObj(file);
      }
      else if(specificTypeString == "D1"){
        return D1BaseFactory::createObj(file);
      }
      else{
        throw std::runtime_error("error");
      }
    }
};

With std::unique_ptr<Base> D1BaseFactory::createObj(std::ifstream & file); returning std::unique_ptr<Derived1>(new Derived1(param1, param2, ...)); and the same thing for `D2BaseFactory'.

Problem is, if I construct a Derived class with the common BaseFactory, and call pFunc() on the returned unique_ptr, it always will be the empty Base::pStruct == {} and thus trying to access members of the covariant pStructs isn't possible. I know this is because the factory createObj returns the base type, but is there any way to dynamically return the type I want so I can access the necessary fields in the derived pStructs? I think using raw pointers might work, but if possible i'd like to keep them as unique pointers.

casey ryan
  • 151
  • 1
  • 2
  • 7
  • 4
    You should be using a `pStruct*` or `some_smart_pointer_type` for the return type, otherwise you are always going to return only a `pStruct` – NathanOliver Aug 12 '21 at 20:50
  • 2
    related/dupe: https://stackoverflow.com/questions/274626/what-is-object-slicing – NathanOliver Aug 12 '21 at 20:50
  • 1
    Also, make the destructor of `Base` `virtual`. – Ted Lyngmo Aug 12 '21 at 20:56
  • @NathanOliver Would I make the return type `pStructD1*` for `Derived1` and D2 for the other, or would they all be the general `pStruct*`? – casey ryan Aug 12 '21 at 20:59
  • 2
    The derived function should be able to return a derived pointer. Upcasting is implicit and covariant IIRC – NathanOliver Aug 12 '21 at 21:00
  • 1
    The return values for all variants of pStruct would be `std::unique_ptr`. The issue, however, is on the calling side. You would have **something** to properly cast pStruct to derived structs... And I am not sure what this something would be in your particular case. – SergeyA Aug 12 '21 at 21:01

1 Answers1

1

You can do something similar to this:

class Base
{
public:
    ...
    std::unique<pStruct> pFunc(...) { return DopFunc(); } 
protected:
    virtual std::unique<pStruct> DopFunc() = 0;
};

class Derived1 : public Base 
{
public:
    struct pStructD1 : Base::pStruct { ... };

    // Used when calling the child factory directly...
    std::unique_ptr<pStructD1> pFunc(...) 
    { 
        return std::make_unique<pStructD1>(...);
    }

protected:
    // Used when called through the Base factory...
    std::unique<pStruct> DopFunc(...) override
    {
        // Call the other function for code sharing... (DRY)
        return pFunc(...);
    }
    ....
};

This could make sense if you used the derived class when you need the derived objects.

The real question is why you need to used the derived types... If it is for initialisation purpose, then maybe the factory should do it before returning the value.

If it is for some processing, then maybe you should have some virtual functions in pStruct. That way, you never need to know the derived type returned by the factory.

In some case, the visitor pattern might also be a solution.

If you need to always use the specific struct, then why not always use the specific factory too?

You can also cast the result but if you need to do it every time you create an object, it might make the code more complex that it need to be.

Alternatively, you could also have a template member function let say template <class T> std::unique_ptr<T> pFuncT(...) { ... }. That way, the client code can specified the desired type directly at construction. An empty object or an exception could be throw if the type is incorrect.

Phil1970
  • 2,605
  • 2
  • 14
  • 15