0

My problem is based on typical diamond hierarchy, but is not a typical diamond problem.

class Interface
{
public:
    int value; 
    // SomeBigData &data; 
    Interface(int _value = 0) : value(_value) {}; 

    virtual void function1() = 0; 
    virtual void function2() = 0; 
};

class ImpOne : public virtual Interface
{
public:
    void function1() { ++value; }
};

class ImpTwo : public virtual Interface
{
public:
    void function2() { --value; }
};

class Device : public ImpOne, public ImpTwo
{
public:
    Device() : Interface(7) {}; 
};

There is an abstract interface defining many function (the example above is simplified). Then there are implementation classes that implement only a few of these functions at a time. And finally, there are a non-abstract classes, like class Device above, implementing the whole interface by combining several implementation classes together.

Now, the problem is this:

There is an integer element in Interface class that gets initialized by class Device and is shared among implementation classes, so far so good. But if I remove the = 0 from Interface(int _value = 0) constructor, the whole system collapses because I am missing default constructor for Interface class, which is a bit odd, because it never gets called.

Why does that bother me? As suggested in code, the Interface class needs to contain reference to complex data structure (not owned by the class), which is than shared among all derived implementation classes. It is however neither sensible nor possible to specify a default value for a reference.

So the question is:

How do I correctly initialize an Interface class (reference) element such as SomeBigData &data suggested in code, where I can’t specify default value for default constructor (which never gets called anyway)? Or am I doing something wrong?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • What do you mean by "the whole system collapses"? – jrok Apr 06 '14 at 13:39
  • The concept of interfaces (which is not a c++ concept) requires an abstract class holding no(!) data, but virtual functions, only. –  Apr 06 '14 at 13:41
  • If the `Interface` class is supposed to provide interface, why does it hold an attribute `value`, aren't the attributes appropriate for implementation classes? – Arun Apr 06 '14 at 13:42
  • You can opt to leave the reference unitialized in the default constructor. It's a weird design indeed, but it'll make the compiler shut up. – jrok Apr 06 '14 at 13:49
  • If the reference is left uninitialized by constructor, it causes an error. – user2828383 Apr 06 '14 at 14:01

3 Answers3

0

You could do this:

class Interface {
public:
    int value; 
    virtual SomeBigData& getBigData() = 0;
    Interface(int _value = 0) : value(_value) {}; 

    virtual void function1() = 0; 
    virtual void function2() = 0; 
};

class BigDataProvider : public virtual Interface {
public:
    SomeBigData& data_;
    BigDataProvider(SomeBigData& data) : data_(data) {}
    SomeBigData& getBigData() { return data_; }
};

class Device : public BigDataProvider, public ImpOne, public ImpTwo {
public:
    Device(SomeBigData& data) : BigDataProvider(data), Interface(7) {}
};

The same pattern could also be used for you member value. Then Interface would be a "pure interface" and you avoid the diamond class layout overall.

Danvil
  • 22,240
  • 19
  • 65
  • 88
  • That's very nice. My original solution was similar, but I rejected it because of extensive getters definitions and usage. – user2828383 Apr 06 '14 at 15:48
  • I mean, if one of these functions calls in a loop other functions and each and every one of those calls needs to load all the (lets say 20) elements/references from distance before performing something potentially trivial, it is a prety waste of computing time (and code lines). If the function can reach elements directly it is much easier and faster to use. And as I demonstrated in example, it can be done (at least with some types of elements). I know that what I am trying to do is not exactly pure, but I am doing it because of efectivity and saving like a hundred of code lines. – user2828383 Apr 06 '14 at 16:11
0

You can declare a default constructor without implementation: (https://ideone.com/ZD336z)

class Interface
{
public:
    Interface(int _value, SomeBigData &data) : value(_value), data(data) {}
    ~Interface() {}

    virtual void function1() = 0;
    virtual void function2() = 0;
protected:
    Interface(); // No implementation
protected:
    int value;
    SomeBigData &data;
};

The method should be visible for derived class, but doesn't need to be implemented as only the other constructor is called.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Yep, that does work neatly. I tried public and =delete before, but somehow I seem to have forgot protected. Thanks much. – user2828383 Apr 06 '14 at 15:42
  • Actually it does not work. For some reason it seemed to have compiled (?puzzled?), but the compiler demands implementation for the code you gave. – user2828383 Apr 06 '14 at 16:39
  • @user2828383: which compiler ? Note that if you derive from `Device`, each concrete child class should call `Interface(value, data)` (as it uses virtual inheritance). – Jarod42 Apr 06 '14 at 16:45
  • @user2828383: Just tested on MSVC 2013, And I just got `warning C4250: 'Device' : inherits 'ImpOne::ImpOne::function1' via dominance`. No error. – Jarod42 Apr 07 '14 at 07:45
  • Well, if I copy exactly what you wrote (from the url), I get a linking error: error LNK2019: unresolved external symbol "protected: __thiscall Interface::Interface(void)" (??0Interface@@IAE@XZ) referenced in function "public: __thiscall ImpOne::ImpOne(void)" (??0ImpOne@@QAE@XZ) – user2828383 Apr 07 '14 at 13:21
0

Moin,

Firstly, I think you have to declare a constructor for ImpOne and ImpTwo as well. Otherwise they'll try to call Interface() when contructed, which doesn't not exist. Secondly, you could avoid the diamond problem by using Composite Pattern.

class Interface                         // abstract
{
protected:
    Base() {}

public:
    virtual void f1(int &value) {}
    virtual void f2(int &value) {}
}

class ImpOne: Base                    
{
public:
    void f1(int &value) { value++; }
}

class ImpTwo : Base                    
{
public:
    void f2(int &value) { value--; }
}

class DeviceBase                        // abstract base for all "Devices"
{
protected:
    int value;
    List<Interface*> Leaves;            // use std::vector or whatever dependent 
                                        // on your library
    Composite(int val) { value = val; }

public:
    void f1()
    {
        for (Interface* const leaf : Leaves)
            leaf->f1(value);
    }

    void f2()
    {
        for (Interface* const leaf : Leaves)
            leaf->f2(value);
    }
}

class Device : DeviceBase
{
public:
    Device(int val) : DeviceBase(val)
    {
        Leaves << new ImpOne(value);
        Leaves << new ImpTwo(value);
    }
}    

Greez Albjeno

Albjenow
  • 369
  • 2
  • 12
  • Firstly it's rather complex, secondly only works as long as all return types are void. Which I admit is the case of example I gave, but it does not apply to my actual program, sorry. – user2828383 Apr 06 '14 at 16:02