带有虚函数的静态多态性
Static polymorphism with virtual functions
这是 的后续问题。因此,似乎静态多态性的最佳解决方案是使用 CRTP。如果你想要运行时多态性,虚函数是一个很好的解决方案。
我发现这不是很优雅,因为问题实际上非常相似(也许你想在某些时候改变行为)但代码实际上非常不同。在我看来,如果解决方案非常相似并且仅在一个地方不同,那么代码会更具表现力。
所以我想知道是否有一种方法可以通过虚函数获得纯静态多态性。这可能类似于属性或某些构造,不允许指向抽象基 class 的指针。有没有这样的功能,如果没有,我是否遗漏了为什么不应该存在这样的功能?静态多态性和运行时多态性实际上是否比此类功能所暗示的更加不同?
编辑:为了让问题和用例更清楚一些,这里有一些例子,我想写:
[[abstract]] class Base {
public:
void bar() { /* do something using foo() */ }
private:
virtual void foo() = 0;
};
class Derived1 : public Base {
public:
Derived1();
private:
void foo() override { /* do something */};
};
class Derived2 : pulic Base {
public:
Derived2();
private:
void foo() override { /* do something with data */ }
int data;
};
其中不存在的属性 [[abstract]] 意味着不存在 class Bass 的实例,即使不是通过指针。这将清楚地表达静态多态性,并且编译器可以优化虚拟调用,因为它们不存在。也不需要虚拟析构函数。
编辑 2:目标是提供一个抽象接口,可以在进一步派生的 classes 中稍作修改,并具有与抽象 class 相同的扩展选项。所以主要实现还在Base
,虚函数的具体实现在Derived
.
你更有可能得到一些相反的东西,运行时多态看起来像静态多态,或者看起来完全不同。
针对 post-reflection C++(也许 c++26)提出的元类提案看起来足够强大,可以做如下事情:
Interface IBob {
void foo();
};
和
Implementation<Dispatch::Static> BobImpl:IBob {
void foo() {}
};
Implementation<Dispatch::Dynamic> BobImpl:IBob {
void foo() {}
};
大致按照您的要求进行。 (元类提案中的语法远非最终版本;然而,表达能力显然可以实现上述目的)。
动态情况下会设置 vtables 等(但可能不是标准 C++ vtables),而在静态情况下 BobImpl
将不相关类型 bob
.
当然,在那个时候,我希望在 C++ 中有很多表达多态性的新方法,以至于“我希望我的 CRTP 像虚函数一样编写 table C++ object “有点像看到原子能技术出现在 horizon 上,并为它可以取代 steam-train 上的煤燃烧器感到兴奋。
So it seems that the best solution for static-only polymorphism is to use CRTP. If you want runtime polymorphism, virtual functions are a good solution.
在您的链接问题中,您使用了 CRTP 来实施接口:
template <typename TData>
struct Base {
void foo() {
static_cast<TData*>(this)->doFoo();
}
这使用了静态多态性,但它是一个非常特殊的用途。如果您派生的 class 没有合适的 doFoo
方法,它什么都不做,只是拒绝编译。所以我不知道你是怎么得出结论的。
I find that not very elegant because the problems are actually very similar (and maybe you want to change the behavior at some point) but the code is actually very different. The code would in my opinion be more expressive if the solutions would be very similar and only differ at a single spot.
你失去了我。运行时多态性影响 C++ 中的两件事:
声明,因为您必须有基础 class、virtual
关键字,以及可选的 override
和 final
使用,当调用点使用virtual dispatch寻找正确的方法实现时
(尽管如前所述,当静态类型已知时,这可能会被优化掉)。
另请注意,虽然多态性经常被讨论为关于对象之间的关系,但在 C++ 中我们实际上只讨论方法分派。
静态多态性仅影响调用站点。您的其他问题使用 CRTP 的事实并不意味着这是使用静态多态性的唯一方法。
如果我写一个模板函数
template <typename T>
void foo_it(T&& t) { t.foo(); }
然后就是使用静态多态。它适用于任何具有合适 foo
方法的 T
,无论它是否源自 Base<T>
。它甚至可以用于 T
覆盖来自其他一些基础 class 的虚拟 foo()
。这是 .
因为不清楚你希望你的[[abstract]] Base
实现什么,我只能建议你只写
class Derived {
public:
void foo();
};
并将其传递给期望某种类型实现 foo()
.
的函数模板
作为您编辑的后续行动
So the main implementation is still in Base and the specific implementation of the virtual functions is in Derived
使用 CRTP 就可以了。这是一个恰好使用静态多态性的实现细节,不是一个类似虚拟的层次结构。
例如
template <typename Derived>
struct Template {
Derived* virt() { return static_cast<Derived*>(this); }
int foo(int i) {
return i + virt->detail(i) + virt->extra();
}
};
struct A: public Template<A> {
int detail(int i) { return i*i; }
int extra() { return 17; }
};
struct B: public Template<B> {
int detail(int i) { return i % 23; }
int extra() { return -42; }
};
创建两个独立的类型 A
和 B
,它们提供相同的接口 int foo(int)
,并且碰巧共享一些代码作为实现细节。
它不创建层次结构。如果您编写一个函数模板,它接受一些 T
类型的对象并在其上调用方法 int T::foo(int)
,这将起作用。那就是静态多态。它不需要共享基础 class.
假设问题是如何在编译时执行派生的 classes
- 实现一个基础“抽象接口”
- 没有多态性
- 保持非 public 的实现,但基础 class 成员可以访问
以下可能是一种方法。
#include <type_traits>
#include <iostream>
using std::cout;
using std::endl;
template<class Impl> class Base {
protected:
void foo() {
Bridge::virtual_foo(static_cast<Impl&>(*this));
}
struct Bridge : public Impl {
static void virtual_foo(Impl &that) {
static constexpr void (Impl::*fn)() = &Bridge::foo;
(that.*fn)();
}
static_assert(std::is_same<void (Impl::*)(), decltype(&Bridge::foo)>::value, "foo not implemented");
};
public:
void bar() {
cout << "begin Base::bar" << endl;
foo();
cout << "end Base::bar" << endl << endl;
}
};
class Good : public Base<Good> {
protected:
void foo() {
cout << "in Good::foo" << endl;
}
};
class Bad : public Base<Bad> {
};
int main() {
Good().bar();
// Bad().bar(); // static_assert: 'foo' not implemented
}
begin Base::bar
in Good::foo
end Base::bar
这是
我发现这不是很优雅,因为问题实际上非常相似(也许你想在某些时候改变行为)但代码实际上非常不同。在我看来,如果解决方案非常相似并且仅在一个地方不同,那么代码会更具表现力。
所以我想知道是否有一种方法可以通过虚函数获得纯静态多态性。这可能类似于属性或某些构造,不允许指向抽象基 class 的指针。有没有这样的功能,如果没有,我是否遗漏了为什么不应该存在这样的功能?静态多态性和运行时多态性实际上是否比此类功能所暗示的更加不同?
编辑:为了让问题和用例更清楚一些,这里有一些例子,我想写:
[[abstract]] class Base {
public:
void bar() { /* do something using foo() */ }
private:
virtual void foo() = 0;
};
class Derived1 : public Base {
public:
Derived1();
private:
void foo() override { /* do something */};
};
class Derived2 : pulic Base {
public:
Derived2();
private:
void foo() override { /* do something with data */ }
int data;
};
其中不存在的属性 [[abstract]] 意味着不存在 class Bass 的实例,即使不是通过指针。这将清楚地表达静态多态性,并且编译器可以优化虚拟调用,因为它们不存在。也不需要虚拟析构函数。
编辑 2:目标是提供一个抽象接口,可以在进一步派生的 classes 中稍作修改,并具有与抽象 class 相同的扩展选项。所以主要实现还在Base
,虚函数的具体实现在Derived
.
你更有可能得到一些相反的东西,运行时多态看起来像静态多态,或者看起来完全不同。
针对 post-reflection C++(也许 c++26)提出的元类提案看起来足够强大,可以做如下事情:
Interface IBob {
void foo();
};
和
Implementation<Dispatch::Static> BobImpl:IBob {
void foo() {}
};
Implementation<Dispatch::Dynamic> BobImpl:IBob {
void foo() {}
};
大致按照您的要求进行。 (元类提案中的语法远非最终版本;然而,表达能力显然可以实现上述目的)。
动态情况下会设置 vtables 等(但可能不是标准 C++ vtables),而在静态情况下 BobImpl
将不相关类型 bob
.
当然,在那个时候,我希望在 C++ 中有很多表达多态性的新方法,以至于“我希望我的 CRTP 像虚函数一样编写 table C++ object “有点像看到原子能技术出现在 horizon 上,并为它可以取代 steam-train 上的煤燃烧器感到兴奋。
So it seems that the best solution for static-only polymorphism is to use CRTP. If you want runtime polymorphism, virtual functions are a good solution.
在您的链接问题中,您使用了 CRTP 来实施接口:
template <typename TData>
struct Base {
void foo() {
static_cast<TData*>(this)->doFoo();
}
这使用了静态多态性,但它是一个非常特殊的用途。如果您派生的 class 没有合适的 doFoo
方法,它什么都不做,只是拒绝编译。所以我不知道你是怎么得出结论的。
I find that not very elegant because the problems are actually very similar (and maybe you want to change the behavior at some point) but the code is actually very different. The code would in my opinion be more expressive if the solutions would be very similar and only differ at a single spot.
你失去了我。运行时多态性影响 C++ 中的两件事:
声明,因为您必须有基础 class、
virtual
关键字,以及可选的override
和final
使用,当调用点使用virtual dispatch寻找正确的方法实现时
(尽管如前所述,当静态类型已知时,这可能会被优化掉)。
另请注意,虽然多态性经常被讨论为关于对象之间的关系,但在 C++ 中我们实际上只讨论方法分派。
静态多态性仅影响调用站点。您的其他问题使用 CRTP 的事实并不意味着这是使用静态多态性的唯一方法。
如果我写一个模板函数
template <typename T>
void foo_it(T&& t) { t.foo(); }
然后就是使用静态多态。它适用于任何具有合适 foo
方法的 T
,无论它是否源自 Base<T>
。它甚至可以用于 T
覆盖来自其他一些基础 class 的虚拟 foo()
。这是
因为不清楚你希望你的[[abstract]] Base
实现什么,我只能建议你只写
class Derived {
public:
void foo();
};
并将其传递给期望某种类型实现 foo()
.
作为您编辑的后续行动
So the main implementation is still in Base and the specific implementation of the virtual functions is in Derived
使用 CRTP 就可以了。这是一个恰好使用静态多态性的实现细节,不是一个类似虚拟的层次结构。
例如
template <typename Derived>
struct Template {
Derived* virt() { return static_cast<Derived*>(this); }
int foo(int i) {
return i + virt->detail(i) + virt->extra();
}
};
struct A: public Template<A> {
int detail(int i) { return i*i; }
int extra() { return 17; }
};
struct B: public Template<B> {
int detail(int i) { return i % 23; }
int extra() { return -42; }
};
创建两个独立的类型 A
和 B
,它们提供相同的接口 int foo(int)
,并且碰巧共享一些代码作为实现细节。
它不创建层次结构。如果您编写一个函数模板,它接受一些 T
类型的对象并在其上调用方法 int T::foo(int)
,这将起作用。那就是静态多态。它不需要共享基础 class.
假设问题是如何在编译时执行派生的 classes
- 实现一个基础“抽象接口”
- 没有多态性
- 保持非 public 的实现,但基础 class 成员可以访问
以下可能是一种方法。
#include <type_traits>
#include <iostream>
using std::cout;
using std::endl;
template<class Impl> class Base {
protected:
void foo() {
Bridge::virtual_foo(static_cast<Impl&>(*this));
}
struct Bridge : public Impl {
static void virtual_foo(Impl &that) {
static constexpr void (Impl::*fn)() = &Bridge::foo;
(that.*fn)();
}
static_assert(std::is_same<void (Impl::*)(), decltype(&Bridge::foo)>::value, "foo not implemented");
};
public:
void bar() {
cout << "begin Base::bar" << endl;
foo();
cout << "end Base::bar" << endl << endl;
}
};
class Good : public Base<Good> {
protected:
void foo() {
cout << "in Good::foo" << endl;
}
};
class Bad : public Base<Bad> {
};
int main() {
Good().bar();
// Bad().bar(); // static_assert: 'foo' not implemented
}
begin Base::bar
in Good::foo
end Base::bar