jme
jme

Reputation: 20765

How can I have an object of a derived class "reference" members of an instance of the base class?

Suppose I have a class Foo whose implementation involves a large object. I want to extend the functionality of Foo, and so I decide to write a class Bar which inherits Foo:

class Foo {
protected:
    int bigobject;
public:
    Foo() : bigobject(1) { }
    int get() { return bigobject; }
    void set(int x) { bigobject = x; }
};


class Bar : public Foo {
public:
    Bar(Foo& f) { /* ??? */ }
    void multiply(int factor) { bigobject *= factor; }
};

As you can see, I've left the constructor blank. This is because what I'd like to do is have the base members of any Bar object be references to members of an existing Foo instance. In other words, I want the code:

Foo f;
f.set(5);
std::cout << "f is " << f.get() << std::endl;

Bar b(f);
std::cout << "b is " << b.get() << std::endl;
b.multiply(2);
std::cout << "b is " << b.get() << std::endl;
std::cout << "f is " << f.get() << std::endl;

To result in:

f is 5
b is 5
b is 10
f is 10

So that f and the base part of b share the same memory space. This is to avoid copying bigobject any time I want to instantiate a Bar. In informal terms, I want a Bar object b to be a "view" of a Foo object f. Calling any of Foo's member methods would change f, but I could also define more methods as part of Bar which would change f as well.

Now, I could declare a global "storage" variable as such:

int bigobject_storage = 1;

class Foo {
protected:
    int& bigobject;
public:
    Foo() : bigobject(bigobject_storage) { }
    int get() { return bigobject; }
    void set(int x) { bigobject = x; }
};

class Bar : public Foo {
public:
    void multiply(int factor) { bigobject *= factor; }
};

To get the functionality I want, but this seems hackish and leaks the implementation. So, is there an idiomatic C++ way to accomplish this?

Upvotes: 0

Views: 132

Answers (2)

Doverman
Doverman

Reputation: 21

You can achieve what you want using nothing more than OOP concepts.

All you need to do is a pointer to the object, and the polymorphism will do the job.

You don't have yo change nothing in your code, nor you have to give Bar a copy of Bar, all you need to do is call the constructor of the base class (although that is done automatically, in the case of the default constructor), in your derived class.

Something like this:

class Foo {
protected:
    int bigobject;
public:
    Foo() : bigobject(1) { }
    int get() { return bigobject; }
    void set(int x) { bigobject = x; }
};


class Bar : public Foo {
public:
    Bar() : Foo() { }
    void multiply(int factor) { bigobject *= factor; }
};

The trick is in deciding how you want to "view" the object. Because Foo is the base class of Bar, Bar has everything that Foo has. In other words, Bar is Foo plus what you define in Bar. So a Foo pointer pointing to Bar will behave as a Foo object, even if it is pointing to Bar.

The code would be like this:

Bar b();
/* 
/* f and b point to the same object.
/* But, to the compiler, f does'nt know anything of b and its methods,
/* because f is a pointer to Foo.
 */
Foo *f = &b;
f->set(5)
std::cout << "f is " << f->get() << std::endl;

std::cout << "b is " << b.get() << std::endl;
b.multiply(2);
std::cout << "b is " << b.get() << std::endl;
std::cout << "f is " << f->get() << std::endl;

To have this result:

f is 5
b is 5
b is 10
f is 10

And even you are saving the memory of one object, because you are using the same object, but viewed as different classes. That is polymorphism.

Upvotes: 0

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 154045

David Wheeler seems to be responsible for the quote

All problems in computer science can be solved by another level of indirection.

This applies in your case: Have your base class Foo store a [smart] pointer to the big object in question and share the corresponding pointer across the relevant instances. When default constructing a Foo object a big object is allocated and pointed to. When copying Foo objects, the new Foo object is set to reference the same big object as the original. Note, however, that this means that the Foo bases behave more like references to each other than values.

In practical terms, you'd probably use something like this:

class Foo {
protected: // I don't really believe in protected data members! Don't use them!
    std::shared_ptr<int> bigobject;
public:
    Foo(): bigobject(new int()) {}
    Foo(Foo const& other): bigobject(other.bigobject()) {}
    // ...
 };

 Bar::Bar(Foo& other): Foo(other) { /*...*/ }

Upvotes: 1

Related Questions