如何避免单个派生 class 实现的虚函数?
How to avoid a virtual function for a single derived class implementation?
我有一个 Accessor
class 定义我与其他 classes 和此 Accessor
[=31= 中的多个基础 class 对象的接口] 实现各种风格的东西。
class Accessor
{
std::shared_ptr<Base> object1;
std::shared_ptr<Base> object2;
}
Accessor
class 实现的当然不仅仅是对不同对象的调用,还有一个特定的函数,它确实只将函数调用重定向到一个对象。
现在的问题是,这个特定的函数应该只在一个派生的 class 中实现,当使用 Accessor
class 调用函数时,众所周知特定对象始终是实现此方法的派生 class。目前,我正在定义一个 'empty' 基础 class 方法并仅在提到的派生 class.
中覆盖它
但是,我不喜欢这种方法,原因有二:首先,从设计的角度来看,它看起来很丑;其次,我还有一个 virtual
函数调用(这里讨论的函数在循环的热路径)。
总而言之,代码的最小版本如下所示:
class Base
{
virtual void f() const { std::cout << "Dummy implementation!\n";}
};
class Derived1 : Base
{
void f() const override { std::cout << "Implementing the actual thing!\n";}
};
class Derived2 : Base
{
// Carrying the dummy implementation of the base class
};
class Accessor
{
std::shared_ptr<Base> object1;
std::shared_ptr<Base> object2;
// When calling this function, we know that we actually
// call object1 of type `Derived1`. Still, there might be
// cases where object1 has a different type, but then we don't
// call this function
void f() const { object1->f();}
}
整个描述可能在某种程度上表明整体设计不是最佳选择。不过,也许还有其他的选择可以重新设计这里的问题,让虚函数调用消失,代码更能反映实际情况。
如果您确定 object_1
始终指向 Derived1
,则将其设为 std::shared_ptr<Derived1> object1
。如果 Base::f()
的唯一目的是 Derived1
,请将其设为非虚拟并将其从不需要它的 classes 中删除。
但如果其中一个假设不正确,请保持原样:
在调用非虚函数之前进行额外的测试以检查class是否为Derived1
并不比直接调用虚函数更有效。如果你不相信我,做一个基准。此外,如果将来您进一步专业化 Derived1
,稍微不同 f()
,您就不必担心了。
假设它总是 Derived1
并且只是静态向下转换是有风险的。如前所述,如果您如此确定,只需将其声明为(转到第一段),并在初始化时进行(安全的)向下转换 object_1
.
备注:虽然一开始有一个f()
在大多数情况下是没用的,这看起来很奇怪,但它实际上是一个常见的在 tell, don't ask paradigm. A very typical example is when using the template method pattern.
中使用多态时的练习
你可以避免基class和虚方法std::variant
,虽然它比虚函数调用慢一点。
您可以测试 Derived1
类型是否在 variant
.
中
class Derived1
{
public:
void f(size_t i) { ++_i; }
private:
size_t _i {0};
};
class Derived2
{
public:
};
class Accessor
{
public:
template<typename S>
void add_one (std::shared_ptr<S> val) {
object1 = val;
}
// When calling this function, we know that we actually
// call object1 of type `Derived1`. Still, there might be
// cases where object1 has a different type, but then we don't
// call this function
void f(size_t i) const {
// you can check the type with this
//if (std::holds_alternative<std::shared_ptr<Derived1>>(object1)) {
std::get<std::shared_ptr<Derived1>>(object1)->f (i);
//}
}
public:
std::variant<std::shared_ptr<Derived1>, std::shared_ptr<Derived2>> object1;
};
int main ()
{
Accessor obj;
obj.add_one (std::make_shared<Derived2>());
return 0;
}
我有一个 Accessor
class 定义我与其他 classes 和此 Accessor
[=31= 中的多个基础 class 对象的接口] 实现各种风格的东西。
class Accessor
{
std::shared_ptr<Base> object1;
std::shared_ptr<Base> object2;
}
Accessor
class 实现的当然不仅仅是对不同对象的调用,还有一个特定的函数,它确实只将函数调用重定向到一个对象。
现在的问题是,这个特定的函数应该只在一个派生的 class 中实现,当使用 Accessor
class 调用函数时,众所周知特定对象始终是实现此方法的派生 class。目前,我正在定义一个 'empty' 基础 class 方法并仅在提到的派生 class.
但是,我不喜欢这种方法,原因有二:首先,从设计的角度来看,它看起来很丑;其次,我还有一个 virtual
函数调用(这里讨论的函数在循环的热路径)。
总而言之,代码的最小版本如下所示:
class Base
{
virtual void f() const { std::cout << "Dummy implementation!\n";}
};
class Derived1 : Base
{
void f() const override { std::cout << "Implementing the actual thing!\n";}
};
class Derived2 : Base
{
// Carrying the dummy implementation of the base class
};
class Accessor
{
std::shared_ptr<Base> object1;
std::shared_ptr<Base> object2;
// When calling this function, we know that we actually
// call object1 of type `Derived1`. Still, there might be
// cases where object1 has a different type, but then we don't
// call this function
void f() const { object1->f();}
}
整个描述可能在某种程度上表明整体设计不是最佳选择。不过,也许还有其他的选择可以重新设计这里的问题,让虚函数调用消失,代码更能反映实际情况。
如果您确定 object_1
始终指向 Derived1
,则将其设为 std::shared_ptr<Derived1> object1
。如果 Base::f()
的唯一目的是 Derived1
,请将其设为非虚拟并将其从不需要它的 classes 中删除。
但如果其中一个假设不正确,请保持原样:
在调用非虚函数之前进行额外的测试以检查class是否为
Derived1
并不比直接调用虚函数更有效。如果你不相信我,做一个基准。此外,如果将来您进一步专业化Derived1
,稍微不同f()
,您就不必担心了。假设它总是
Derived1
并且只是静态向下转换是有风险的。如前所述,如果您如此确定,只需将其声明为(转到第一段),并在初始化时进行(安全的)向下转换object_1
.
备注:虽然一开始有一个f()
在大多数情况下是没用的,这看起来很奇怪,但它实际上是一个常见的在 tell, don't ask paradigm. A very typical example is when using the template method pattern.
你可以避免基class和虚方法std::variant
,虽然它比虚函数调用慢一点。
您可以测试 Derived1
类型是否在 variant
.
class Derived1
{
public:
void f(size_t i) { ++_i; }
private:
size_t _i {0};
};
class Derived2
{
public:
};
class Accessor
{
public:
template<typename S>
void add_one (std::shared_ptr<S> val) {
object1 = val;
}
// When calling this function, we know that we actually
// call object1 of type `Derived1`. Still, there might be
// cases where object1 has a different type, but then we don't
// call this function
void f(size_t i) const {
// you can check the type with this
//if (std::holds_alternative<std::shared_ptr<Derived1>>(object1)) {
std::get<std::shared_ptr<Derived1>>(object1)->f (i);
//}
}
public:
std::variant<std::shared_ptr<Derived1>, std::shared_ptr<Derived2>> object1;
};
int main ()
{
Accessor obj;
obj.add_one (std::make_shared<Derived2>());
return 0;
}