4

Say I wish to define a member variable in a parent class and set its value in an inherited class. Perhaps these identify functionality available in the class or the nature of the child class. For example:

class A
{
public:
    inline int getX() { return x; }
protected:
    const int x = 0;
};

class B : public A
{
protected:
    const int x = 10;
};

class C : public A
{
protected:
    const int x = 50;
};

It should go without saying that scope issues will prevent the above from working properly. However, is there a way to make this work as intended?

Since the variable is meant to identify the nature of the inherited classes, I would prefer if it were const - this problem would not arise if it were not const and merely redefined in the constructor, so far as I can tell.

  • 1
    Since **the variable is meant to identify the nature of the inherited classes**, it makes a lot more sense to use polymorphism. That's the basic reason for which it was "invented". In other words, declare `virtual int getX()` and implement this function in each class, or simply use RTTI (`dynamic_cast`) on each object in order to "discover its nature". – barak manos May 23 '15 at 11:36
  • @barakmanos Part of the goal here was to make every attempt to get the function to inline, as this will get called a lot. That way is standard, but given how often this is going to be used in what I am doing, even a slight reduction of overhead is worth it. Since virtual members usually won't inline, I wanted to keep the implementation in the base class. Based on examination of the assembly (which I make no claim to be an expert on), it seems at least for my simple test case, the function will inline this way. –  May 23 '15 at 11:47
  • @WilliamKappler is it important that B & C derive from A, or is it that you want them to have a similar interface? If the latter, there is a 'faster' way to do it which I can show you. – Richard Hodges May 23 '15 at 11:58
  • Well just make it public in that case and avoid the function-call altogether. – barak manos May 23 '15 at 12:00
  • @RichardHodges It is; this is actually a slightly less convoluted example of exactly what I am doing. I have to identify which of B or C I have when holding only an A (interface) pointer, so I can know if I can cast to a B or C. –  May 23 '15 at 12:02
  • @barakmanos You're right that I could have it const and public, and I will look at if that works better. However, in either case, getter or public const, I'd still have the issue I was addressing. –  May 23 '15 at 12:04
  • @WilliamKappler ok your solution will work, but this is called duck typing. It's an evil way to write software. Polymorphism will give you much better maintainability at the cost of almost zero performance. I note the desire to inline and understand, but politely suggest that you give the optimiser and instruction pipeline more credit - i think you'll find the performance difference is zero. – Richard Hodges May 23 '15 at 12:05
  • @RichardHodges It's not really as bad as it might seem, partially because my example used an int, while in real life I have only a bool. The two classes are an actual class (which actually does stuff) and a class which merely stores copied data from an actual class. Ergo, it's true if it's the real thing, false if it's a copy, so I know any commands to operate on the data will be ignored if calling them from the copy. The class names also bear that out pretty readily. –  May 23 '15 at 12:10
  • @WilliamKappler I understand, and as a friendly parting gesture offer this question and answer: "what is difference in cpu cycles to perform a conditional jump (which is the result of testing a bool and executing one of two code flows) and performing a call to an indirect address?" - the answer is that half the time, the indirect call is hundreds of times faster, because it does not cause a pipeline flush. I strongly believe you're building in an anti-optimisiation. – Richard Hodges May 23 '15 at 12:15
  • @WilliamKappler see my answer for a demonstration on how polymorphism is actually faster. I have provided a fully compilable example which will work on windows, linux and OSX. – Richard Hodges May 23 '15 at 12:58

2 Answers2

8

While fiddling with the compiler trying to make sure my example code made sense, I actually came across the fact that the way I was attempting to define the constants was C++11-specific. That led me to look into the ways it was done before, and I found this question, which shed some light on the matter indirectly.

Defining a variable in this way should be done by having the base class take an argument in its constructor, in the form of:

class A
{
public:
    A( const int& type ) : x(type) {}
    inline int getX() { return x; }
protected:
    const int x;
};

class B : public A
{
public:
    B() : A(10) {}
};

class C : public A
{
public:
    C() : A(50) {}
};

This will work as intended and allow the constant x to be redefined by inherited classes.

Community
  • 1
  • 1
  • 1
    It also won't hurt to make `getX()` a const member. `A::A()` need not accept a reference in this case. – Peter May 23 '15 at 13:07
0

To demonstrate the point I made in my comment, here is an example of what I think you're trying to do (deduced from comments).

I have provided both duck-typed and polymorphic solutions in the same program with a timed run through each.

I use 10 million samples of each to eliminate memory cache noise.

You will notice that the run time of the polymorphic solution is significantly less than that of the duck-typed solution.

#ifdef _WIN32
#include <Windows.h>

double get_cpu_time(){
    FILETIME a,b,c,d;
    if (GetProcessTimes(GetCurrentProcess(),&a,&b,&c,&d) != 0){
        //  Returns total user time.
        //  Can be tweaked to include kernel times as well.
        return
        (double)(d.dwLowDateTime |
                 ((unsigned long long)d.dwHighDateTime << 32)) * 0.0000001;
    }else{
        //  Handle error
        return 0;
    }
}
#else
#include <sys/time.h>

inline double get_cpu_time() noexcept {
    return (double)clock() / CLOCKS_PER_SEC;
}

#endif


#include <iostream>
#include <vector>
#include <memory>

struct A
{
    A(bool copy_) : copy{copy_} {}

    virtual ~A() = default;
    const bool copy = false;
};

struct RealA : public A
{
    RealA() : A { false } {}
};

struct CopyA : public A
{
    CopyA() : A { true } {}
};

// A Thing holder will hold any object which has an interface supports do_something_to(T& thing)

struct AHolder {

    template<class Thing>
    AHolder(std::unique_ptr<Thing> ptr)
    : _ptr { std::move(ptr) }
    {

    }

    template<class Thing, class...Args>
    static AHolder construct(Args&&...args)
    {
        return AHolder { std::make_unique<model<Thing>>(std::forward<Args>(args)...) };
    }

    void do_something() const {
        _ptr->do_something();
    }
private:
    struct concept {
        virtual ~concept() = default;

        virtual void do_something() = 0;
    };
    template<class Thing> struct model : concept {
        template<class...Args>
        model(Args&&...args) : _thing { std::forward<Args>(args)... } {}
    private:
        void do_something() override {
            do_something_to(_thing);
        }
        Thing _thing;
    };

    std::unique_ptr<concept> _ptr;
};


using namespace std;

size_t copies_processed = 0;
size_t reals_processed = 0;

void do_something_to(const CopyA&)
{
    // simulate work
    ++copies_processed;
}

void do_something_to(const RealA&)
{
    // simulate work
    ++reals_processed;
}


int main(int argc, char **argv) {

    std::vector<std::unique_ptr<A>> duck_typing;
    std::vector<AHolder> polymorphic;

    constexpr size_t samples = 10000000;

    for (size_t i = 0 ; i < samples ; ++i) {
        if (i % 2) {
            duck_typing.push_back(make_unique<RealA>());
            polymorphic.emplace_back(AHolder::construct<RealA>());
        }
        else {
            duck_typing.push_back(make_unique<CopyA>());
            polymorphic.emplace_back(AHolder::construct<CopyA>());
        }
    }

    auto duck_start = get_cpu_time();
    // nasty duck-typing solution
    for (const auto& ptr : duck_typing) {
        if (ptr->copy) {
            do_something_to(*(static_cast<CopyA*>(ptr.get())));
        }
        else {
            do_something_to(*(static_cast<RealA*>(ptr.get())));
        }
    }
    auto duck_stop = get_cpu_time();

    auto poly_start = get_cpu_time();
    for (const auto& a_like : polymorphic) {
        a_like.do_something();
    }
    auto poly_stop = get_cpu_time();

    cout << "duck typing : " << duck_stop - duck_start << endl;
    cout << "polymorphic : " << poly_stop - poly_start << endl;

    cout << "copies processed : " << copies_processed << endl;
    cout << "reals processed : " << reals_processed << endl;

    return 0;
}

sample output :

duck typing : 0.162985
polymorphic : 0.137561
copies processed : 10000000
reals processed : 10000000
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • I appreciate that you went to considerable effort, but I think you're missing the point of what I am doing, which is understandable since I have not explained it all. Mostly, my calls to both classes are identical and devoid of type checks, and the copy classes' function implementations merely do nothing. However, there are many cases where I need to ensure I am only dealing with actual classes - otherwise readback would fail, for example, and could cause unexpected behavior. Other examples include indexing (stored in different containers) and serialization (actual objects have more data). –  May 23 '15 at 13:03
  • 1
    @WilliamKappler Of course I completely understand. Both our examples are trivial. I just wanted to demonstrate that although it seems counter-intuitive, polymorphic solutions are faster than if-then-else solutions when data sets are large, because the randomness of the data will cause a cache-miss very often. Polymorphic solutions don't suffer from the instruction cache-miss problem because there are no conditional jumps, and the values of the indirect jumps remain cached. The technique I have used is called 'polymorphism as an implementation detail' - very powerful. – Richard Hodges May 23 '15 at 13:08