【问题标题】:Obtain variable from derived class c++从派生类c++中获取变量
【发布时间】:2018-04-10 16:34:11
【问题描述】:

我只想在类是特定派生类时才做某事。那就是我有:

class X{
    int id;
}

class A: public X{
    void run();
}

class B: public X{
    int lala;
}

我想做一些事情:

main(){
    vector<X *> types;
    types.push_back(new A);
    types.push_back(new B);
    int var = 0;
    for(int i = 0; i<types.size(); i++){
        if(types[i].isType(A)) {types[i].run();} 
    }
    for(int i = 0; i<types.size(); i++){
        if(types[i].isType(B)) {var = lala;} 
    }
}

我不希望 B 类有任何等价于 run() 的东西,我也不希望 A 类有等价于 lala。

我知道 fortran 有一个解决方法

select type ( x => var )
class is ( A )
    x.run()
end select

但我不确定我在 C++ 中的选择是什么。

谢谢

【问题讨论】:

  • 那么X只是为了让它们保持在同一个向量中?

标签: c++ class oop derived


【解决方案1】:

您正在寻找dynamic_cast

#include <vector>
using namespace std;

class X {
public:
    int id;
    virtual ~X() = default;
};

class A : public X {
public:
    void run() {}
};

class B : public X {
public:
    int lala;
};

main(){
    vector<X *> types;
    types.push_back(new A);
    types.push_back(new B);
    int var = 0;
    for(int i = 0; i<types.size(); i++){
        if (auto ta = dynamic_cast<A *>(types[i])) {
            ta->run();
        }
    }
    for(int i = 0; i<types.size(); i++){
        if (auto tb = dynamic_cast<B *>(types[i])) {
            var = tb->lala;
        }
    }
}

也可以在这里查看它的实际效果:https://onlinegdb.com/B1d29P5if

我不得不修复代码的其他一些问题。由于它们不是您问题的一部分,因此我不会在这里澄清,但欢迎您询问是否有不清楚的地方。

编辑:上述解决方案存在内存泄漏,我没有修复,因为问题不需要。为了完整起见,这里是修复了内存泄漏的主要函数 (https://onlinegdb.com/ByeOmu9iz):

int main() {
    vector<unique_ptr<X>> types;
    types.emplace_back(new A);
    types.emplace_back(new B);
    int var = 0;
    for(int i = 0; i < types.size(); ++i) {
        if (auto ta = dynamic_cast<A *>(types[i].get())) {
            ta->run();
        }
    }
    for(int i = 0; i < types.size(); ++i) {
        if (auto tb = dynamic_cast<B *>(types[i].get())) {
            var = tb->lala;
        }
    }
}

请注意,这是一个 C++11 解决方案。

如果您使用的是更旧的编译器,则必须继续使用原始解决方案中的普通指针,并在最后通过在向量的每个元素上调用 delete 手动释放内存。 (并希望在您到达那一步之前没有任何异常。) 您还必须将auto ta 替换为A* ta,将auto tb 替换为B* tb

【讨论】:

  • 修复漏洞不会有什么坏处。
  • 谢谢戈兰。我想还有一个小问题,如果 A 和 B 有派生类,那么 if 语句对派生类是否有效。也就是说,如果 C 是从 A 派生的,并且我们使用“if (auto ta = dynamic_cast(types[i]))”动态转换 C 的类型,那会返回 true 吗?
  • @ChristianHackl 我知道,我想更改最少量的 codo 以使其编译 - 问题不在于泄漏 :) 但我将添加具有固定泄漏的代码以供参考。
  • @DaveLass 是的,如果C 派生自A,它也可以工作,它将通过A 的路径。请注意,@Rabster 的解决方案并非如此。
  • 我还想指出,过度使用dynamic_cast 通常是代码设计不佳的标志。我不知道您的确切应用程序,但如果您尝试对对象集合执行操作,这取决于对象的运行时类型,也许您正在寻找访问者设计模式:en.wikipedia.org/wiki/Visitor_pattern
【解决方案2】:

解决此问题的现代 C++17 方法是使用变体向量,即std::vector&lt;std::variant&lt;A, B&gt;&gt;。为此,您需要一个现代编译器。

这是一个完整的例子,基于std::variant documentation

#include <vector>
#include <variant>
#include <iostream>

class X {
    int id;
};

class A: public X {
public:
    void run() {
        std::cout << "run\n"; // just for demonstration purposes
    }
};

class B: public X {
public:
    B(int lala) : lala(lala) {} // just for demonstration purposes
    int lala;
};

int main() {
    std::vector<std::variant<A, B>> types;

    types.push_back(A()); // no more new!
    types.push_back(B(123)); // no more new!

    int var = 0;

    for (auto&& type : types) {
        std::visit([&](auto&& arg) {
            using T = std::decay_t<decltype(arg)>;
            if constexpr (std::is_same_v<T, A>) {
                arg.run();
            } else {
                var = arg.lala;
            }
        }, type);
    }

    std::cout << var << '\n'; // just for demonstration purposes
}

作为一个不错的奖励,这个解决方案优雅地摆脱了动态分配(不再有内存泄漏,不需要智能指针)。

【讨论】:

  • 这是解决这个问题的好方法(并且比dynamic_cast 变体执行得更好),但我只想提一下,如果我们将新类添加到层次结构中,它将不再起作用,并且还想将它们添加到types。除非我们改变向量的签名。
【解决方案3】:

我有两个想法......

为什么不提供一个共享方法来返回一个值,该值给出关于它是 A 还是 B 的上下文?例如,如果预计 lala 只返回 0 或更大的值,您可以使用 void run() 代替 int run() 并始终返回 -1。

class X {
   int id;
   virtual int run() = 0; //Assuming X isn't meant to be instantiated
}

class A: public X {
   // Return -1 to differentiate between As and Bs
   int run() { return -1; }
}

class B: public X {
   int lala;
   int run() { return lala;}
}

那么你有...

main(){
vector<X *> types;
types.push_back(new A);
types.push_back(new B);
int var = 0, temp = 0;

for( int i = 0; i<types.size(); i++ ) {
    if( (temp = types[i].run()) != -1 )
        var = temp;
        ....
    }
}

同样,仅当 lala 不希望返回特定范围的值时才有效。

您还可以在创建 A 或 B 时隐藏 X 中的信息以跟踪您拥有的内容。

class X {
    int id;
    bool isA;
}

class A: public X {
    A() : isA(true) { };
    void run();
}

class B: public X {
    B() : isA(false) { } ;
    int lala;
}

那么你有...

main(){
vector<X *> types;
types.push_back(new A);
types.push_back(new B);
int var = 0;

for( int i = 0; i<types.size(); i++ ) {
    if( types[i].isA == true ) {
        types[i].run();
    }
    else {
        var = types[i].lala;
    }
}

当然,如果您希望添加 C、D、E、...。这将不再值得,但对于只有两个派生类来说,这并不是那么糟糕。

我会根据这样一个事实来证明这一点,即用户已经将不得不查看派生类以了解为什么它们从同一个类派生的行为如此不同。我实际上会研究 A 和 B 基于它们的接口从 X 派生是否有意义。

我也不会推荐 dynamic_cast(ing) 而不告知某人这是更危险的演员之一,通常不推荐。

【讨论】:

    【解决方案4】:

    您可以使用dynamic_cast 来检查基类指针是否可转换为派生实例。

    另一种选择是拥有一个返回类的typeinfo 的虚函数,从而使用该信息将指针转换为可转换类型。根据dynamic_cast 的实现方式,这可能会更高效。因此,如果您想尝试查看此方法在您的平台上是否更快,则可以使用此方法。

    正如@Jarod42 所指出的,您需要有一个虚函数,在这种情况下为析构函数,dynamic_cast 才能工作。此外,您只需要一个虚拟析构函数即可避免在删除实例时出现未定义的行为。

    例子

    #include <iostream>
    #include <string>
    #include <vector>
    #include <typeinfo>
    
    struct A {
        virtual ~A() {
    
        }
    
        virtual const std::type_info& getTypeInfo() const {
            return typeid(A);
        }
    };
    
    struct B : public A {
        virtual const std::type_info& getTypeInfo() const override {
            return typeid(B);
        }
    };
    
    struct C : public A {
        virtual const std::type_info& getTypeInfo() const override {
            return typeid(C);
        }
    };
    
    
    
    int main()
    {
        std::vector<A*> data;
        data.push_back(new A);
        data.push_back(new B);
        data.push_back(new C);
    
        for (auto& val : data) {
            if (val->getTypeInfo() == typeid(A)) {
                std::cout << "A";
            }
            else if (val->getTypeInfo() == typeid(B)) {
                std::cout << "B";
            }
            else if (val->getTypeInfo() == typeid(C)) {
                std::cout << "C";
            }
            std::cout << std::endl;
        }
    
        for (auto& val : data) {
            delete val;
        }
    }
    

    【讨论】:

    • dynamic_cast 至少需要一个虚拟方法(作为析构函数)。
    • “这通常比dynamic_cast 性能更高” - 为什么编译器要实现一些功能完全相同但速度较慢的东西?请注明出处。
    • getTypeInfo 的定义可能会被到处复制粘贴。在这里必须非常小心。
    • 你仍然必须以某种方式投射它才能访问它的成员。您可以使用不会引入任何开销的static_cast,除非由于多重继承而需要横向转换。那么无论如何你都必须使用dynamic_cast。这也回答了@ChristianHackl 的问题——编译器必须为dynamic_cast 做一些更聪明的事情来支持横向转换。
    • 我应该尽量避免做出这样的概括:P
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-01
    • 2013-11-18
    • 2018-05-19
    • 2022-06-10
    • 2019-02-18
    • 2011-07-20
    • 2018-01-03
    相关资源
    最近更新 更多