13

I'm implementing a simple hierarchy: System and Device would be two base classes. A System has Devices. Then I'd have a Network derived from System, which will have Computers. Computer is derived from both System and Device. And finally a Computer has various Devices like CPU, Memory etc. The reason Computer is a Device is because Network is a System and Systems store Devices. The reason Computer is a System is because it stores Devices (CPU, Memory etc.)

Anyway, my implementation so far:

class Device {
public:
    explicit Device(unsigned property): property(property) {}
    friend void swap(Device &first, Device &second) {
        using std::swap;
        swap(first.property, second.property);
    }
    Device& operator=(Device other) {
        swap(*this, other);
        return *this;
    }
private:
    Device() = default;

    unsigned property;
};

class System {
public:
    explicit System(const string &name): name(name) {}
    friend void swap(System &first, System &second) {
        using std::swap;
        swap(first.name, second.name);
        swap(first.devices, second.devices);
    }
    System& operator=(System other) {
        swap(*this, other);
        return *this;
    }
protected:
    void addDevice(Device b1) {
        devices.push_back(b1);
    }
private:
    vector<Device> devices;
    string name;
};

class Computer: public System, public Device {
public:
    Computer(unsigned property, const string& name): System(name), Device(property) {}

    Computer& addAddress(const string &address) {
        addresses.emplace_back(address);
        return *this;
    }
    Computer& addComponent(Device newDevice) {
        System::addDevice(newDevice); //error
        return *this;
    }
private:
    vector<const string> addresses;
};

class Network: public System {
public:
    Network(const string& name): System(name) {}

    Network& addComputer(Device newComputer) {
        System::addDevice(newComputer); //no error
        return *this;
    }
};

class CPU: public Device {
public:
    CPU(unsigned coreCount, unsigned frequency): Device(coreCount), frequency(frequency) {}
private:
    unsigned frequency;
};

Computer's addComponent() has an error because cannot initialize object parameter of type System with an expression of type Computer. I'm not entirely sure what that means since that function is called from a Computer class, which is a Device hence it should have access to the parent's addDevice(). The parameter the function has is a Device.

It's also confusing that my Network class, where the function addComputer() uses the same set of instructions has no error.

Could someone explain how my design is flawed?

Sorry
  • 532
  • 2
  • 6
  • 19
  • _"Could someone explain how my design is flawed?"_ Those `friend` declarations smell a lot. – πάντα ῥεῖ Apr 25 '19 at 19:16
  • 4
    @πάνταῥεῖ that's the way I understood copy-swap idiom: https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom – Sorry Apr 25 '19 at 19:17
  • 2
    You can't have vector of `const string`. If you fix that error, it compiles. I suspect that the message refers to your use of the function. – molbdnilo Apr 25 '19 at 19:51
  • looks like the assignment operators are an attempt at getting around the `const string`. They can go because all of the member variables are trivially copyable or support the [Rule of Three or Five](https://en.cppreference.com/w/cpp/language/rule_of_three). This means you can use the Rule of Zero. – user4581301 Apr 25 '19 at 20:32

3 Answers3

12

This error message popped up for me today, while I was in the processes of coding the derived class, and in my case the key was that the derived class was--just at that moment--un-instantialble.1

As soon as I made enough progress on the derived class for it to be instantiated the errors evaporated.

Cause

The derived call from which you are typing to invoke the base class is currently un-instantiable.

Demonstration

Consider the following code which has two inheritance relationships.

class Type {
public:
    Type(){}
    virtual ~Type() {}
    virtual void m() = 0;
};

class SubType: public Type {
public:
    SubType(): Type() {}
    virtual ~SubType() {}
    // virtual void m() override; // <== Important code commented out here
};
// void SubType::m() {}           // <== and here


class Base {
public:
    Base() {};
    virtual ~Base() {}
    virtual void m();
};

class Derived: public Base, public Type {
public:
    Derived(): Base() {}
    virtual ~Derived() override {}
    virtual void m() override;
protected:
    SubType m_troublesome;  // <== Note member here has incomplete type when
                            //     comments are in place
};

void Base::m() {}

void Derived::m() {
    Base::m();              // <== Tricky error message on this line
}

Attempt to compile this with

$ clang --std=c++11 -Wall -pedantic -c test.cc

and you receive the feedback

test.cc:34:13: error: field type 'SubType' is an abstract class
    SubType m_troublesome;
            ^
test.cc:8:18: note: unimplemented pure virtual method 'm' in 'SubType'
    virtual void m() = 0;
                 ^
test.cc:42:11: error: cannot initialize object parameter of type 'Base' with an
      expression of type 'Derived'
    Base::m();

If you see all three together you know where to start, but if you are using clang as a linter (as many IDEs do), then you may see the third one (which occurs on the line Base::m();, a perfectly valid piece of code in isolation) in a context divorced from the first two.

Instant confusion.

If you remove the comments from the two marked sections of code (making SubType complete and thus making it possible to instantiate Derivedobjects), the code compiles cleanly.

Two possible lessons here:

  • Finish what you start. If I hadn't left the analog of SubType half done the situation would never have arisen.
  • If the linter is being confusing look at the full error output of the build.

1 In particular I had a member declared with a type in which I hadn't yet overridden an inherited abstract virtual method.

dmckee --- ex-moderator kitten
  • 98,632
  • 24
  • 142
  • 234
0

You can't have std::vector of base class instances and save derived instances to it. Use pointers to base class in vector, e.g. std::vector<std::shared_ptr<Device>>

user2807083
  • 2,962
  • 4
  • 29
  • 37
0

I had a circular include that caused this error. It was between the child and the parent. For some reason each was including the other, during a refactor.

Ian A McElhenny
  • 910
  • 8
  • 20