3

I am trying to use a pure virtual class as a parameter in a program, however, i get a compile error:

Error 1 error C2259: 'Person': cannot instantiate abstract class

I guess the error im getting is because A) its not possible to instantiate an abstract class and B) i can't use an abstract class as i would use an interface in C#

The C# program below illustrates what im trying to do in the C++ program. How can i write generic code using abstract classes in C++? if im forced to use a more specialized version of Person e.g. Employee, the code is not really generic. Do i have to use templates?

C++ Program

#include<iostream>
#include<vector>

class Person {
    public:
        virtual std::string getName() = 0;
        virtual void setName(std::string name) = 0;
        virtual std::string toString() = 0;
    private:
        std::string name;
};

class Employee : public Person {
    public:
        std::string getName() {
            return this->name;
        }

        void setName(std::string name) {
            this->name = name;
        }

    std::string toString() {
        return "name:" + this->name;
    }

    private:
        std::string name;
};

class Repository {
    public:
        void add(Person& p) {
            this->repo.push_back(p);
        }
    private:
        std::vector<Person> repo;
};

int main(int argc, char* argv[])
{
    Repository repo;

    Employee emp1;
    emp1.setName("John Doe");

    repo.add(emp1);

    return 0;
}

C# Program

interface IPerson
{
    string GetName();
    void SetName(string name);
    string ToString();
}

class Employee : IPerson
{
    private string _name;

    public string GetName() {
        return this._name;
    }

    public void SetName(string name) {
        this._name = name;
    }

    public override string ToString() {
        return "name: " + this._name;
    }
}

class Repository
{
    private List<IPerson> _repo;

    public Repository() {
        this._repo = new List<IPerson>();
    }

    public void Add(IPerson p) {
        this._repo.Add(p);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Repository repo = new Repository();

        Employee emp1 = new Employee();
        emp1.SetName("John Doe");

        repo.Add(emp1);
    }
}
user1359448
  • 1,539
  • 3
  • 14
  • 23
  • In C# putting `string ToString();` in an interface is at least a little "strange", because all the `object`- derived objects (so nearly everything) have an overloadable `string ToString();` – xanatos Jul 01 '15 at 06:46

3 Answers3

3

The problem is that Repository is storing Person objects, and this class cannot be instantiated*. This is because std::vector<Person> holds Person values.

You can store pointers to Person instead, but you have to ensure they live at least as long as the Repository instance. For example,

/// class repository does not own Persons it holds
class Repository {
    public:
        void add(Person& p) {
            this->repo.push_back(&p);
        }
    private:
        std::vector<Person*> repo;
};

* Note that in general it is possible to construct a base class object from a derived type one. The base object would be constructed from the base sub-object of the derived one (see What is object slicing?). In your case, this fails because the base type is abstract.

Community
  • 1
  • 1
juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • You should explain that `repo.push_back` wants to create a trimmed down "copy" of `Employee` (the [object slicing](https://en.wikipedia.org/wiki/Object_slicing) problem) – xanatos Jul 01 '15 at 06:49
  • @xanatos `repo` stored `Person` objects. It can't store anything else. I think that is clear enough. – juanchopanza Jul 01 '15 at 06:51
  • It is very clear for you, it is clear enough for me that don't program anymore C++, but it isn't clear for anyone that is coming from C# or Java. Object slicing isn't intuitive for those persons, nor is the abundant use of the copy constructor that happens "behind the scenes" in C++. – xanatos Jul 01 '15 at 06:52
  • @xanatos I think object slicing is a distraction. You cannot store things that cannot be instantiated. I'll add a foot-note. – juanchopanza Jul 01 '15 at 06:52
  • Even if `Person` was instantiable (because it was a base class, instead of being a pure abstract class), the code wouldn't work in the way the questioner think. You correctly sidestep the whole problem by using pointers (and by opening a big can of worms that the casual reader won't notice until the program breaks: *but you have to ensure they live at least as long as the Repository instance*, but we will ignore this)... – xanatos Jul 01 '15 at 06:57
1

Your problem is that memory handling of C++ is totally different from memory handling of C#. Totally totally.

std::vector<> stores copies of what you add to it. copies is the important word. The other problem is that the copy constructor can't be virtual (see for example Can we make a class copy constructor virtual in C++).

Now... what happens is that when you do this->repo.push_back(p) the std::vector<> searches in Person for a copy constructor (because you are using a std::vector<Person>) and it doesn't find it, so the error.

Note that in general, even if Person wasn't an abstract class, your code would probably still be wrong, because std::vector<> wouldn't copy an Employee to another Employee, it would copy an Employee to a sliced-down Person (this is normally called object slicing) (remember that the copy constructor isn't virtual?)...

For the solution, start with the one given by @juanchopanza, but remember about the sentence You can store pointers to Person instead, but you have to ensure they live at least as long as the Repository instance... it is very important!

Similar question to yours: c++: can vector<Base> contain objects of type Derived? There the accepted answer suggests using std::vector<std::unique_ptr>.

Community
  • 1
  • 1
xanatos
  • 109,618
  • 12
  • 197
  • 280
1

C# has value types (structs) and reference types (classes). C++ has only value types + at least three different workarounds for that language design decision; none of them are are problem free.

Boost provides a poor-mans garbage collector in terms of shared_ptr<T>. Using that you get C#'ish semantics in terms of polymorphy and multiple names can reference the same instance. That is ofcourse broken too, because it relies on reference counting and RAII, so it doesn't deal with circular dependencies; for that you need to use weak_ptr<T> to break the circle. I guess some of these smart pointers made it into the recent C++ standard.

You will eventually find that working with C++ ends up being more about using the language correctly than making software that is good solutions to your problems.