概念指针数组



我试图弄清楚我是否可以使用概念作为类的一种接口,而不需要虚拟表的开销。 我整理了一个示例,该示例有效,但我必须将我的类实例存储在由其公共继承而不是其公共概念定义的数组中。 我没有在关于概念数组的帖子中看到任何讨论的内容,但 g++ 6.3.0 似乎不允许这样做。 错误是:

$ g++ -fconcepts -std=c++1z custom_concept.cpp 
custom_concept.cpp: In function ‘int main()’:
custom_concept.cpp:37:20: error: ‘shapes’ declared as array of ‘IShape*’
IShape* shapes[2] = {&square, &rect};  // doesn't work 
^
custom_concept.cpp:39:25: error: ‘shapes’ was not declared in this scope
for (IShape* shape : shapes ) 
^~~~~~

如果我将IShape*数组更改为Rectangle*数组(如导致第一个错误的数组下方的注释行行(,程序将按预期编译和运行。

为什么不允许使用概念指针数组? 在未来的 c++ 版本中是否可能允许这样做?

(我的例子包括虚函数和继承,尽管我的目标是消除它们。 我包含它们只是为了方便Rectangle*版本工作。 如果我能让IShape*版本工作,我计划删除虚拟函数和继承。

这是代码:

#include <iostream>
template <typename T>
concept bool IShape = requires (T x, T z, int y)
{
{ T() } ;
{ T(x) }  ;
{ x = z } -> T& ;
{ x.countSides() } -> int ;
{ x.sideLength(y) } -> int ;
};
struct Rectangle
{
Rectangle() {};
Rectangle(const Rectangle& other) {};
Rectangle& operator=(Rectangle& other) {return *this; };
virtual std::string getName() { return "Rectangle"; }
int countSides() {return 4;}
virtual int sideLength(int side) { return (side % 2 == 0) ? 10 : 5; }
};
struct Square : public Rectangle
{
Square() {};
Square(const Square& other) {};
Square& operator=(Square& other) {return *this; };
std::string getName() override { return "Square"; }
int sideLength(int side) override { return 10; }
};
int main()
{
Square square;
Rectangle rect;
IShape* shapes[2] = {&square, &rect};  // doesn't work 
//  Rectangle* shapes[2] = {&square, &rect}; // works 
for (IShape* shape : shapes )
{
for (int side = 0 ; side < shape->countSides() ; ++side )
{
std::cout << shape->getName() << " side=" << shape->sideLength(side) << "n";
}
}
return 0;
};

感谢@Yakk关于使用元组的想法。 G++ 6.3.0 还没有完全实现包含 apply(( 的 #include 文件,如 C++17 标准定义的那样,但它在 std::experimental 中可用。 (我认为它可以添加到g++的更高版本中。这是我最终得到的:

#include <iostream>
#include <tuple>
#include <experimental/tuple>
template <typename T>
concept bool IShape = requires (T x, T z, int y)
{
{ T() } ;
{ x = z } -> T& ;
{ T(x) }  ;
{ x.countSides() } -> int ;
{ x.sideLength(y) } -> int ;
};
struct Rectangle
{
Rectangle() {};
Rectangle(const Rectangle& other) {};
Rectangle& operator=(Rectangle& other) {return *this; };
std::string getName() { return "Rectangle"; }
int countSides() {return 4;}
int sideLength(int side) { return (side % 2 == 0) ? 10 : 5; }
};
struct Square
{
Square() {};
Square(const Square& other) {};
Square& operator=(Square& other) {return *this; };  
std::string getName() { return "Square"; }
int countSides() {return 4;}
int sideLength(int side) { return 10; }
};
void print(IShape& shape)
{
for (int side = 0 ; side < shape.countSides() ; ++side )
{
std::cout << shape.getName() << " side=" << shape.sideLength(side) << "n";
}
};
int main()
{
Square square;
Rectangle rect;
auto shapes = std::make_tuple(square, rect);
std::experimental::apply([](auto&... shape) { ((print(shape)), ...); }, shapes) ;
return 0;
};

这做不到。

我的意思是你可以实现自己的类型擦除来替换virtusl函数表。 在您的特定情况下,它可能比 vtable 性能更高,因为您可以针对您的确切问题进行泰勒。

要从编译器获得帮助,这样您就不必编写样板/粘附代码,您需要反射和化支持以及侧面概念。

如果这样做,它将如下所示:

ShapePtr shapes[2] = {&square, &rect};

ShapeValue shapes[2] = {square, rect};

现在,这不会在性能方面做您希望的所有事情;类型擦除仍然会跳过函数指针。 并具有每个对象或视图的存储开销。 但是,您可以用更多的存储换取更少的间接性。

这里的手动类型擦除基本上是用 C 语言实现一个对象模型,然后将其包装成看起来漂亮的C++。 默认的C++对象模型只是一种可能的方法,C 程序实现了许多替代方案。

您也可以退后一步,用元组替换数组。 元组可以存储非均匀类型,并且通过 bkt 的工作,您可以迭代它们:

auto shapes = make_IShapePtr_tuple(&square, &rect);
foreach_elem( shapes,[&](IShape* shape )
{
for (int side = 0 ; side < shape->countSides() ; ++side )
{
std::cout << shape->getName() << " side=" << shape->sideLength(side) << "n";
}
});

其中 lambda 获取非类型擦除类型。

这些都不需要概念:

auto shapes = std::make_tuple(&square, &rect);
foreach_elem( shapes,[&](auto* shape )
{
for (int side = 0 ; side < shape->countSides() ; ++side )
{
std::cout << shape->getName() << " side=" << shape->sideLength(side) << "n";
}
});

以上可以用 C++14 编写。

c++17foreach_elem如下所示:

template<class T, class F>
void foreach_elem( T&& t, F&& f ) {
std::apply( [&](auto&&...args){
( (void)f(decltype(args)(args)), ... );
}, std::forward<T>(t) );
}

在 C++14 中,lambda 中的行改为:

using discard=int[];
(void)discard{ 0,((void)f(decltype(args)(args)),0)... };

这有点迟钝,需要实现std::apply.

在 c++11 中,你必须在外面编写一个模仿 c++14 lambda 的结构。

我知道你想做什么,但这对你的用例没有意义。概念是在编译时强制实施接口的方法,通常用于模板函数。这里想要的是一个抽象接口 - 一个带有一些纯虚拟成员函数的基类。

template <ShapeConcept S, ShapeConcept U>
bool collide(S s, U u)
{
// concrete types of S and U are known here
// can use other methods too, and enforce other concepts on the types
}

抽象接口在运行时强制执行接口 - 您不知道具体类型是什么,但您可以使用提供的方法。

bool collide(ShapeInterface& s, ShapeInterface& u)
{
// concrete types of S and U are unknown
// only methods of interfaces are available
}

附带说明一下,也许这只是一个人为的例子,但正方形肯定不是面向对象意义上的矩形。一个简单的例子是,有人可以在矩形基类中包含一个名为stretch的方法,并且您必须在正方形中实现它。当然,只要你在任何维度上拉伸一个正方形,它就不再是正方形了。小心。

Yakk 的答案是正确的,但我觉得它太复杂了。 从某种意义上说,你的要求是错误的,因为你试图"免费"获得一些你不能免费获得的东西:

试图弄清楚我是否可以使用概念作为类的一种接口,而不需要虚拟表的开销。

答案是否定的。这并不是因为虚拟表的开销是一些不必要的成本。 如果要使用形状数组,则需要存储有关特定实例的信息。 虚拟机器为您执行此操作(最简单的方法是每个实例的隐藏枚举成员,它在运行时告诉编译器要调用哪些成员函数(,如果您愿意,您可以手动执行此操作,但您必须以某种方式执行此操作(例如您可以使用std::variant<Square,Rectangle>(。

如果你不这样做,指向形状的指针数组与指向 void 的指针数组一样好。你不知道你的指针指向什么。

注意:如果您确实由于虚拟开销而难以提高性能,请考虑使用 Boost polly_collection

相关内容

  • 没有找到相关文章

最新更新