0

Is there a way to return different variations of an abstract class in a function without losing information to slicing?

As in, let's say I have an abstract class Dog, and for dog there are WildDog, DomesticDog, Puppy.

Is it possible to create a function that takes in one of these function and return them without slicing information?

Example:

// Returns the type of dog you currently have
Dog getCurrentDog() {
    return this.dog;
}

void setDog(Dog dog) {
    this.dog = dog;
}

Is it possible to pass in WildDog or PuppyDog to setDog and retain all the information in the respective classes. Say for an example PuppyDog has a drinkMilk() function that the other dogs don't have, but I still want to be able to access it.

And if possible, what would the Java equivalent be?

The solution I'm thinking of right now would be to have a getDog() for each of the instances of Dog that return the specific instance of the dog, whether it be Puppy or WildDog. That way, I can specifically access the code in each class.

Jacob Macallan
  • 959
  • 2
  • 8
  • 29
  • In c++, you can return a pointer or reference to a instance. You cannot return a copy without slicing because the compiler has no way of telling how much memory to reserve for an instance of a subclass based on its superclass. Are you talking about in Java, or C++? – Joel Cornett Feb 10 '16 at 00:28
  • In Java, there will be no slicing, because all objects are handled through a reference. In C++, if you don't want slicing, you should pass by reference: `void setDog(const Dog& dog)`. – Klitos Kyriacou Feb 10 '16 at 00:29
  • Ah, so I simply pass by reference in C++. @Klitos; It's weird because in my Eclipse IDE it'll tell me that "Dog" has no method "drinkMilk" and therefore won't compile. – Jacob Macallan Feb 10 '16 at 00:30
  • At least Java/C++ brings some variety in the constant C/C++. But honestly, which language? – Baum mit Augen Feb 10 '16 at 00:31
  • I'm talking about both. I want to know just how both languages will allow me to do what I want above. – Jacob Macallan Feb 10 '16 at 00:31
  • 3
    @Xari *Say for an example PuppyDog has a drinkMilk() function that the other dogs don't have, but I still want to be able to access it.* -- Then you have a design flaw if the function you're calling is a "general" `Dog` function. – PaulMcKenzie Feb 10 '16 at 00:32
  • @PaulMckenzie Thanks for responding. But I was wondering ... not all of the dog classes should have every function that every other dog has. What if the WildDog has a special skillset that allows it to see in the night and thus therefore has an ability like "toggleNightVision" that the other dogs don't have? – Jacob Macallan Feb 10 '16 at 00:36
  • 1
    @Xari "It's weird because in my Eclipse IDE it'll tell me that "Dog" has no method "drinkMilk" and therefore won't compile" - I'm not sure why that's surprising to you, since Dog *doesn't* have a method `drinkMilk`. – user253751 Feb 10 '16 at 00:36
  • @immibis It's surprising to me because someone said the methods would be accessible since Java is pass by reference. – Jacob Macallan Feb 10 '16 at 00:37
  • @Xari - What language are you referring to when you say "Eclipse IDE"? In C++, you will get a compile error since `drinkMilk` does not exist for class `Dog`. – PaulMcKenzie Feb 10 '16 at 00:38
  • @PaulMckenzie I'll stick with Java for the rest of this then, as the Dog problem I asked about is merely a substitute to a problem I'm currently on. Don't get me wrong, I understand that we can't access drinkMilk because it doesn't exist in class Dog; I was just wondering if maybe... I wrote another class called DogClinic and it contained a vector of all types of dogs and I wanted those dogs to do things that may be exclusive to only them. The way Java slices information therefore eliminates those exclusive functions – Jacob Macallan Feb 10 '16 at 00:40
  • In Java you could do `if (dog instanceof PuppyDog) ((PuppyDog) dog).drinkMilk();` but as everyone is saying, it's a sign of a design flaw. – Klitos Kyriacou Feb 10 '16 at 00:44
  • Thank you. I simply have to read up more on design coding then. This was really informative. – Jacob Macallan Feb 10 '16 at 00:45
  • @Xari No, Java is (strictly) [pass-by-value](http://stackoverflow.com/q/40480/3425536). – Emil Laine Feb 10 '16 at 01:01

2 Answers2

4

To avoid slicing, you need to pass a pointer or reference to a Dog. See object slicing.

std::shared_ptr<Dog> getCurrentDog()
{
    return dog;
}

In order to use functions in PuppyDog etc, you would need to declare them virtual in your abstract Dog class, or dynamic_cast. dynamic_cast usually indicates poor design, but as it is, all dogs can drink milk, so it makes sense to declare them in your Dog class:

class Dog
{
    public:
        virtual void drinkMilk(); // not pure virtual: all dogs can drink milk
};

class PuppyDog : public Dog
{
    public:
       // puppies drink less milk, and do it more messily
       void drinkMilk() override;
};

Now you can have the following:

 // getCurrentDog is a PuppyDog
 std::shared_ptr<Dog> myDog = someOwner.getCurrentDog();
 myDog->drinkMilk();

There are plenty of instances this works for: play(), rollOver(), fetch(), and all other things all dogs can do; however, say you had some functionality that not all dogs could do, and only a PuppyDog could. You could still create a function in your base class, and could declare it pure virtual, or you could dynamic_cast to a PuppyDog:

// This if will only succeed if getCurrentDog is a PuppyDog (or something inherited PuppyDog) and if getCurrentDog != nullptr
if (std::shared_ptr<PuppyDog> myPuppy = std::dynamic_pointer_cast<PuppyDog>(someOwner.getCurrentDog()))
{
     // do something only a puppy would:
     myPuppy->beExtremelyCute();
}

As @PaulMcKenzie points out, it is quite often a design flaw and drifts away from object-oriented programming (because you end up with "if it's a WildDog, do this; if it's a PuppyDog, do this" etc)

Community
  • 1
  • 1
Tas
  • 7,023
  • 3
  • 36
  • 51
  • How about in the case where I solely want a puppy to drink milk? I understand in your case you have it defined in the Dog class and then overriden in PuppyDog, but what if I want it solely in PuppyDog. Would the pass by reference solve that? – Jacob Macallan Feb 10 '16 at 00:33
  • You would need to `dynamic_cast`. I will add an example – Tas Feb 10 '16 at 00:33
  • And like my comment suggested above, usage of `dynamic_cast` may be a sign of a design flaw. You'll just fall right into the `if it's this object, then do this` type of coding, which is non-OO. – PaulMcKenzie Feb 10 '16 at 00:36
  • Ah, okay. So then I'd just have to reevaluate how I design my doggies then. I was just curious how I would be able to access some exclusive methods in a dog class while maybe appending them to a vector or something of the sort. – Jacob Macallan Feb 10 '16 at 00:38
  • Thank you for your explanation; so I should define any exclusive methods into the Dog class and make them virtual so that I can choose to override them if I wish to; otherwise, they don't do anything and are empty? – Jacob Macallan Feb 10 '16 at 00:44
  • @Xari Yes, your "Dog" functions have to be general. The trick is can you make a function "general" and be able to utilize custom functions? Making the Dog object have virtual functions and have them empty in the derived classes would work, but IMO kind of naive. There are tricks that can be done, and those tricks can be put into play with a good read on design patterns. – PaulMcKenzie Feb 10 '16 at 00:53
  • you are missing `virtual` for `Dog::drinkMilk()` – Slava Feb 10 '16 at 01:31
0

polymorphism is often better expressed as an implementation detail of a wrapper class. This gives you a heterogeneous interface which can, for example be stored in vectors and copied without slicing.

I have included a working example below.

Notice that the implementation of the various types of dog are not actually polymorphic - dog, puppy and wild_dog.

The polymorphism is introduced as an implementation detail of the general dog class, doggie.

#include <iostream>
#include <typeinfo>
#include <memory>
#include <string>
#include <utility>
#include <type_traits>
#include <vector>

// default cases
template<class T>
void do_howl(const T&)
{
    std::cout << "a " << T::dog_type() << " cannot howl" << std::endl;
}

template<class T>
void do_be_cute(const T&)
{
    std::cout << "a " << T::dog_type() << " is not cute" << std::endl;
}


// now provide specialisations
struct dog
{
    dog(std::string s) : name(std::move(s)) {}
    std::string name;
    static const char* dog_type() { return "normal dog"; }
};

void do_howl(const dog& d)
{
    std::cout << "the dog called " << d.name << " is howling" << std::endl;
}


struct puppy
{
    puppy(std::string s) : name(std::move(s)) {}
    std::string name;
    std::string cute_noise() const { return "yip yip!"; }
    static const char* dog_type() { return "puppy"; }
};

void do_be_cute(const puppy& p)
{

    std::cout << "aww! the cute little puppy called " << p.name << " is barking: " << p.cute_noise() << std::endl;
}

struct wild_dog
{

    static const char* dog_type() { return "wild dog"; }
};

void do_howl(const wild_dog& d)
{
    std::cout << "the nameless wild dog called is howling" << std::endl;
}

struct doggy {

    struct concept {

        virtual void be_cute() const = 0;
        virtual void howl() const = 0;

    };

    template<class T>
    struct model : concept
    {
        model(T&& t) : _t(std::move(t)) {}
        model(const T& t) : _t(t) {}

        void be_cute() const override
        {
            do_be_cute(_t);
        }

        void howl() const override
        {
            do_howl(_t);
        }

        T _t;
    };

    void howl() const {
        _impl->howl();
    }

    void be_cute() const {
        _impl->be_cute();
    }

    template<class T, std::enable_if_t<!std::is_base_of<doggy, T>::value>* = nullptr>
    doggy(T&& t) : _impl(std::make_shared<model<T>>(std::forward<T>(t)))
    {}


    std::shared_ptr<concept> _impl;
};

int main()
{
    std::vector<doggy> dogs = {
        doggy(dog("rover")),
        doggy(puppy("poochums")),
        doggy(wild_dog())
    };

    for (const auto& d : dogs)
    {
        d.howl();
        d.be_cute();
    }

    return 0;
}

expected output:

the dog called rover is howling
a normal dog is not cute
a puppy cannot howl
aww! the cute little puppy called poochums is barking: yip yip!
the nameless wild dog called is howling
a wild dog is not cute

In this particular case, the doggie has shared-implementation semantics. If you wanted distinct copies to be made, replace the shared_ptr with a unique_ptr, provide a copy constructor and add a clone() method to the concept (with corresponding implementation in the model).

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142