0

How can I create mixin classes that share same members? I know this is probably a bad design choice but it is my last resort.

here is an example of what I mean. though they are not sharing the same pointer.

struct Full{
    int a = 213;
    int b = 500;
    int c = 400;
};

struct ProxyA{
    std::shared_ptr<Full> full;
    void say_a(){
        std::cout << full->a;

    }
};

struct ProxyB{
    std::shared_ptr<Full> full;

    void say_b(){
        std::cout << full->b;

    }
};

struct FullProxy: public ProxyA, public ProxyB{
    std::shared_ptr<Full> full;
    FullProxy(std::shared_ptr<Full> full_):
    full{full_}{};
    void say_c(){
        std::cout << full->c << "\n";
    }
};


int main() {
    FullProxy foo(std::make_shared<Full>());
    foo.say_a();
    foo.say_b();
    foo.say_c();
}

output:

ASM generation compiler returned: 0
Execution build compiler returned: 0
Program returned: 139
Program terminated with signal: SIGSEGV

godbolt link

Note that I can't use templates because these classes should have Q_PROPERTYs and Qt doesn't support templates. I realized that Qt does not support multiple inheritance so it won't work for Qt.

ניר
  • 1,204
  • 1
  • 8
  • 28
  • While I agree with @Oersted about giving us details about the original and underlying problem you try to solve, and why you need three different `bar` members, as a possible workaround why not initialize (or at least assign to) both `A::bar` and `B::bar` as well? – Some programmer dude Jul 11 '23 at 08:35
  • @Someprogrammerdude I added a more clear example. "as a possible workaround why not initialize (or at least assign to) both A::bar and B::bar as well?" It would cost another ref count ig though I'll try. – ניר Jul 11 '23 at 08:44
  • 1
    _I would cost another ref count ig though I'll try._ ??? `std::shared_ptr` already manages a ref count internally. – Scheff's Cat Jul 11 '23 at 08:45
  • are you looking for virtual inheritance? – 463035818_is_not_an_ai Jul 11 '23 at 08:46
  • 2
    I understand the issue with `full` in `ProxyA` and `ProxyB`, but why you add another `full` in `FullProxy` is unclear – 463035818_is_not_an_ai Jul 11 '23 at 08:48
  • 1
    A shared pointer is really *two* pointers: One pointer to the actual data, and one pointer to a *shared* control block. The reference counter is located in this shared control block. And all shared pointers to the same object will share this single control block. So there's only one reference counter. Perhaps you mean that creating more shared objects will *increase* the reference counter? Well what is the problem with that? – Some programmer dude Jul 11 '23 at 08:49
  • Please tell us about the actual and underlying problem you need to solve? Please tell us the requirements that lead to your design and your code needing *three* shared pointers to the same object. Right now your question is really too much of an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) to be able to answer in a good way. – Some programmer dude Jul 11 '23 at 08:51
  • There is nothing preventing you *creating* classes with members having the same name, within a hierarchy. The concern is ensuring you can unambiguously name AND access the intended member when needed from other code. One way of doing that (assuming the members are accessible, which is the case if members are `public` and inheritance is `public` as it is in your code) is to use scoping. For example, in `main()`, do `std::cout << foo.ProxyA::full->a << ' ' << foo.ProxyB::full->b << ' ' << foo.FullProxy::full->c << '\n';` – Peter Jul 11 '23 at 08:59
  • 1
    @Someprogrammerdude The actual problem is a graphql client codegen I am writing. objects could appear in many places in the schema this is why they are splitted to proxy objects and concrete types. I need mixins since graphql query can include fragments that are basically partial objects. few fragments can appear on the same object. Though I can't see how is that of any value to you. – ניר Jul 11 '23 at 08:59
  • 1
    And, yes, your design is broken. But, without information on what you are ACTUALLY trying to achieve, it's impossible to suggest alternative (better!) design approaches. An obvious thought is to use aggregation rather than inheritance though. – Peter Jul 11 '23 at 09:02

3 Answers3

3

In your code FullProxy has 3 full members. FullProxy does "share" that member with its ancestors. It does inherit it. Its not clear if it is intended that FullProxy has access to all 3 of them (because in your example everything is public). However, if you want all of the 3 say_x methods use the same member, you can use virtual inheritance:

#include <iostream>
#include <memory> 

struct Full{
    int a = 213;
    int b = 500;
    int c = 400;
};
struct GrandParent {
    std::shared_ptr<Full> full;
};

struct ProxyA : virtual GrandParent {
    void say_a(){
        std::cout << full->a << "\n";    
    }
};

struct ProxyB : virtual GrandParent {
    void say_b(){
        std::cout << full->b << "\n";    
    }
};

struct FullProxy: public ProxyA, public ProxyB{
    FullProxy(std::shared_ptr<Full> full_):
    GrandParent{full_}{};
    void say_c(){
        std::cout << full->c << "\n";
    }
};


int main() {
    FullProxy foo(std::make_shared<Full>());
    foo.say_a();
    foo.say_b();
    foo.say_c();
}

FullProxy inherits GrandParent via ProxyA and via ProxyB, but due to the virtual inheritance FullProxy has only a single full member. Also note that with virtual inheritance, the most derived class is responsible for initializing the virtual base.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Unfortunately Qt does not support multiple inheritance so my question was wrong in the first place though, for the sake of truth I accepted this. – ניר Jul 12 '23 at 15:53
  • @ניר why does it not support multiple inheritance? Anyhow then I do not understand the question because if you dont use multiple inheritance then there are no seperate members to begin with – 463035818_is_not_an_ai Jul 12 '23 at 15:57
  • The MOC (Meta Object Compiler) doesn't support it I don't know the specific reason. I updated that the question is irrelevant for Qt. – ניר Jul 12 '23 at 15:58
  • @ניר I suppose you refer to this https://stackoverflow.com/q/8578657/4117728. Though this isnt apparent from your example. I mean you can inherit as much as you like, as long as `QObject` is not involved. You can use multiple inheritance, only from `QObject` you need to inherit once only – 463035818_is_not_an_ai Jul 13 '23 at 07:20
  • Yeah but I need properties and signals... – ניר Jul 13 '23 at 07:23
  • 1
    @ניר If you inherit from `QObject` in `FullProxy` then I dont see the issue. THe linked Q&A has other workarounds. Nevermind, you already realized that the question you asked wasnt the one you actually wanted to ask, you could open a new question. – 463035818_is_not_an_ai Jul 13 '23 at 07:25
1

You need FullProxy to let the base class proxies know about the Full object to use. If you do that, you don't really need an extra shared pointer in the FullProxy itself - just wastes space and time.

struct FullProxy: public ProxyA, public ProxyB{
        FullProxy(std::shared_ptr<Full> full):
    ProxyA{full}, ProxyB{full} { }
};


int main() {
    FullProxy foo(std::make_shared<Full>());
    // std::cout << foo.full->a;   // doesn't make sense
    foo.say_a();
    foo.say_b();
}
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
0

CRTP to the rescue!

Here is a way similar to CRTP but without templates.

#include <iostream>
#include <memory>

struct Full{
    int a = 213;
    int b = 500;
    int c = 400;
};

struct ProxyA {
    void say_a();
};

struct ProxyB {
    void say_b();
};

struct FullProxy: public ProxyA, public ProxyB {
    std::shared_ptr<Full> full;
    
    FullProxy(std::shared_ptr<Full> full_):
        full{full_} {}

    void say_c(){
        std::cout << full->c << "\n";
    }
};

void ProxyA::say_a() {
    std::cout << static_cast<FullProxy*>(this)->full->a;
}

void ProxyB::say_b() {
    std::cout << static_cast<FullProxy*>(this)->full->b;
}

int main() {
    FullProxy foo(std::make_shared<Full>());
    foo.say_a();
    foo.say_b();
    foo.say_c();
}

This way you only have one full member in the most derived class and the mixin classes can access that member. It is important that say_a and say_b are defined out-of-line after the definition of FullProxy.

chrysante
  • 2,328
  • 4
  • 24
  • With the original design, you could have a `ProxyA` and/or `ProxyB` to access any `Full` object, but with your version `say_a()` and `say_b()` will cast inappropriately with undefined behaviour (likely crash) if not part of a `FullProxy` class. – Tony Delroy Jul 12 '23 at 04:04
  • @TonyDelroy That is true, but OP specifically asked for the two classes to share the same data member. In the end I assume it's a trade-off between memory footprint and usability. – chrysante Jul 12 '23 at 14:00