C++ 在继承的类中重新定义常量成员变量

C++ 在继承的类中重新定义常量成员变量,c++,inheritance,polymorphism,C++,Inheritance,Polymorphism,假设我希望在父类中定义一个成员变量,并在继承的类中设置其值。也许这些标识了类中可用的功能或子类的性质。例如: 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

假设我希望在父类中定义一个成员变量,并在继承的类中设置其值。也许这些标识了类中可用的功能或子类的性质。例如:

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;
};
不用说,范围问题将妨碍上述功能正常工作。然而,有没有办法使这项工作如期进行


因为变量是用来标识继承类的性质的,所以我更希望它是const——据我所知,如果它不是const并且只是在构造函数中重新定义,这个问题就不会出现了。

当我摆弄编译器试图确保我的示例代码有意义时,实际上,我遇到了这样一个事实,即我试图定义常量的方式是特定于C++11的。这让我研究了以前的做法,我发现,这间接地揭示了这一问题

以这种方式定义变量应通过让基类在其构造函数中采用以下形式的参数来完成:

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) {}
};

这将按预期工作,并允许通过继承类重新定义常数x。

为了演示我在评论中提出的观点,下面是一个我认为您正在尝试做的示例(从评论中推断)

我在同一个程序中提供了duck类型和多态解决方案,并对每种解决方案进行了定时运行

我使用了1000万个样本来消除内存缓存噪音

您会注意到多态解决方案的运行时间明显少于duck类型解决方案的运行时间

#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;
}

因为变量是用来标识继承类的性质的,所以使用多态性更有意义。这就是它被“发明”的基本原因。换句话说,声明
virtual int getX()
并在每个类中实现此函数,或者在每个对象上简单地使用RTTI(
dynamic\u cast
)以“发现其本质”。@barakmanos这里的目标之一是尽一切努力使函数内联,因为这将被大量调用。这种方法是标准的,但考虑到我在做什么时经常使用这种方法,即使稍微减少开销也是值得的。由于虚拟成员通常不会内联,所以我希望将实现保持在基类中。基于对程序集的检查(我并不声称自己是这方面的专家),至少在我的简单测试用例中,函数将以这种方式内联。@WilliamKappler B&C从A派生是重要的,还是您希望它们具有类似的接口?如果是后者,有一种“更快”的方法,我可以告诉你。在这种情况下,我们只要公开它,完全避免函数调用。@richardhoges,就是这样;这实际上是一个稍微不太复杂的例子,正好说明了我在做什么。当我只持有A(接口)指针时,我必须确定我拥有B或C中的哪一个,这样我就可以知道我是否可以转换为B或C。我感谢你付出了相当大的努力,但我认为你没有抓住我正在做的事情的重点,这是可以理解的,因为我没有全部解释。大多数情况下,我对这两个类的调用是相同的,并且没有类型检查,复制类的函数实现也没有做任何事情。然而,在许多情况下,我需要确保我只处理实际的类,否则回读将失败,例如,并可能导致意外的行为。其他例子包括索引(存储在不同的容器中)和序列化(实际对象有更多的数据)。@WilliamKappler当然我完全理解。我们的两个例子都微不足道。我只是想证明,尽管这似乎违反直觉,但在数据集很大的情况下,多态解决方案比if-then-else解决方案更快,因为数据的随机性会经常导致缓存丢失。多态解决方案不会遇到指令缓存未命中问题,因为不存在条件跳转,并且间接跳转的值保持缓存状态。我使用的技术称为“多态性作为实现细节”——非常强大。将
getX()
作为常量成员也不会有什么坏处<代码>A::A()在这种情况下不需要接受引用。
duck typing : 0.162985
polymorphic : 0.137561
copies processed : 10000000
reals processed : 10000000