【问题标题】:How to design a template class with switchable member variables at compile-time in C++?如何在 C++ 编译时设计具有可切换成员变量的模板类?
【发布时间】:2020-06-29 03:55:27
【问题描述】:

| ■ 问题定义____________________

我正在尝试设计一个满足以下属性的超级灵活但内存高效的模块。

  1. 它可以根据情况关闭不必要的成员变量
  2. 它将拥有哪些变量在编译时确定

我用枚举器标志”编译时间做了一个确定其成员列表的方法/强>。见下文:

▼ TraitSwitch.h

#pragma once
// Macros to switch-off source codes themselves.
#define ON                 1
#define OFF                0

#define TRI_AREA_INFO      ON
#define TRI_CENTROID_INFO  ON
#define TRI_NORMAL_INFO    OFF // When the normal vector info is unnecessary.
...

▼ TriangleTraits.h

#pragma once
#include <cstdint>
#include "TraitSwitch.h"

enum TriangleTrait : uint8_t
{
    NONE          = 0,   // 0000 0000

#if (TRI_AREA_INFO == ON)
    AREA          = 1,   // 0000 0001
#endif

#if (TRI_CENTROID_INFO == ON)
    CENTROID      = 2,   // 0000 0010
#endif

#if (TRI_NORMAL_INFO == ON) //        | Inactive Preprocessor Block
    NORMAL_VECTOR = 4,   // 0000 0100 |
#endif
    ... // more traits

    ALL           = 255  // 1111 1111
}
// Need some additional overloaded bitwise-operators (&, |, |=, etc ...)

▼ Triangle.h

#pragma once
#include "TriangleTraits.h"

class Triangle
{
public:
    Triangle() {}
    ~Triangle() {}

#if (TRI_AREA_INFO == ON)
    double area;
#endif

#if (TRI_CENTROID_INFO == ON)
    double centroid[3]; // x, y, z
#endif

#if (TRI_NORMAL_INFO == ON) //   | Inactive Preprocessor Block
    double normal[3]; // x, y, z |
#endif
    ...

    TriangleTrait alreadyComputed; // To avoid redundant works.
    void ComputeTraits(TriangleTrait _requested)
    {
        if (((_requested & TriangleTrait::AREA) != 0) 
            && ((alreadyComputed & _requested) == 0))
        {
            this->ComputeArea();
            alreadyComputed |= TriangleTrait::AREA;
        }
        ... // do the same things for centroid, normal
    }

private:
    void ComputeArea();
    void ComputeCentroid();
    void ComputeNormal();
    ...
}

那么,对象上的C++ IntelliSense可能会显示:this

▼ main.cpp

#include <iostream>
#include "Triangle.h"

int main(void)
{
    Triangle tri;
    tri.ComputeTraits(TriangleTrait::AREA | TriangleTrait::CENTROID);

    std::cout << "area : " << tri.area << "m²" << std::endl;
    std::cout << "centroid : (" 
        << tri.centroid[0] << "," 
        << tri.centroid[1] << "," 
        << tri.centroid[2] << ")" << std::endl;
    ...
}

首先Triangle.h看起来挺难看的,就算好看,这个方法也判断类成员"before"无论如何,编译时


| ■ 问题摘要____________________

“如何设计具有可切换成员的模板类,这些成员在编译时确定。”

这正是我想要的:

▼ main.cpp

...

int main(void)
{
    Triangle<__MACRO_DEFINED_TRAIT_SWITCH(AREA)> tri1; // This owns area info only
    tri1.area;

    Triangle<__MACRO_DEFINED_TRAIT_SWITCH(AREA | CENTROID)> tri2; // This owns area & centroid info
    tri2.area;
    tri2.centroid;

    Triangle<__MACRO_DEFINED_TRAIT_SWITCH(AREA | NORMAL)> tri3; // This owns area & normal vector info
    tri3.area;
    tri3.normal;
    ...

    Triangle<__MACRO_DEFINED_TRAIT_SWITCH(AREA | CENTROID | NORMAL)> tri4; // This owns area & centroid & normal vector info
    tri4.area;
    tri4.centroid;
    tri4.normal;
    ...
}

我猜想使用 模板与宏 结合(使用 标签调度 方法,也许?)将完全符合我的要求,但没有任何明确的想法。

【问题讨论】:

  • '在运行时确定'。由于模板类是在编译时确定的,因此您将永远无法在运行时确定它(显然对于宏也是如此)。这似乎是一个死胡同。
  • @Jean 哦,我很困惑 3:编译前 & 编译时 & 运行时 .我会立即编辑我的问题。感谢您指出我的愚蠢错误! :D
  • 听起来像你想要的std::tuple
  • @L.F. |是的,当然std::tuple 很简单,很好。但我认为调用像double&amp; area = std::get&lt;0&gt;(tri) 这样的每个特征并不能提供很好的代码可读性。另外,参数的顺序对它来说太关键了。:(

标签: c++ c++11 templates visual-c++


【解决方案1】:

经过几天的苦恼,我找到了办法。所以我会自己回答我的问题。

灵魂太简单了。只需使用可变参数模板的多重继承。见下文:

▼TriangleTraits.h

struct AREA
{
    double area;
};

struct CENTROID
{
    double centroid[3];
};

struct NORMAL
{
    double normal[3];
};
... // more traits

// Multiple Inheritances with variadic template
template<class... Traits>
struct TriangleWithTraits : Traits... 
{
};

▼main.cpp

#include "TriangleTraits.h" // Just include this one is enough

int main()
{
    TriangleWithTraits<AREA> tri1;
    tri1.area;

    TriangleWithTraits<AREA, CENTROID> tri2;
    tri2.area;
    tri2.centroid;

    TriangleWithTraits<AREA, NORMAL> tri3;
    tri3.area;
    tri3.normal;

    TriangleWithTraits<AREA, CENTROID, NORMAL> tri4;
    tri4.area;
    tri4.normal;
    tri4.centroid;
...
}

【讨论】:

    【解决方案2】:

    无法使用预处理器解析模板参数,预处理器不知道C++。

    但是有 std::conditional 和 Empty Base 优化。

    因此您可以拥有带有数据类部分的基础,并std::conditional 在它们之间进行选择并为空基础:

    struct EmptyBase {};
    
    struct BaseWithChar
    {
        char a;
    };
    
    struct BaseWithLongLong
    {
        long long b;
        long long c;
    };
    
    template<int Flags>
    struct A
        : std::conditional<(Flags & 1), BaseWithChar, EmptyBase>::type
        , std::conditional<(Flags & 2), BaseWithLongLong, EmptyBase>::type
    {
    };
    
    int main() {
        std::cout << sizeof(A<1>) << std::endl;
        std::cout << sizeof(A<2>) << std::endl;
        std::cout << sizeof(A<3>) << std::endl;
        return 0;
    }
    

    随着后来的 C++ 标准,有 std::coditional_t[[no_unique_address]]

    using namespace std;
    
    struct EmptyMember {};
    
    template<int Flags>
    struct A
    {
        [[no_unique_address]] typename std::conditional_t<(Flags & 1), char, EmptyMember> a;
        [[no_unique_address]] typename std::conditional_t<(Flags & 2), long long, EmptyMember> b;
        [[no_unique_address]] typename std::conditional_t<(Flags & 2), long long, EmptyMember> c;
    };
    
    int main() {
        std::cout << sizeof(A<1>) << std::endl;
        std::cout << sizeof(A<2>) << std::endl;
        std::cout << sizeof(A<3>) << std::endl;
        return 0;
    }
    

    但是[[no_unique_address]] 需要C++20,没有[[no_unique_address]] 空成员会占用一些空间。看起来它不适用于 Visual C++,即使是 2019 预览版。我已经发布了feebdack

    使用早期的C++,当你没有std::conditionalboost::conditional时,你可以轻松实现自己的,它只是一个真假特化的模板。请参阅cppreference page上的“可能实现”

    【讨论】:

    • 感谢您的热情回复! :D 我不熟悉std::conditional,所以我不得不自己研究一下。我发现 EmptyMember 只占用 1 个字节的内存,所以可以说内存效率很好。但是这个方法离我想要的有点远,因为类仍然必须拥有其中的所有特征,即使它们不占用太多内存。因此,每当您在对象上按 Ctrl+SpaceBar 时,每个成员都会出现在建议窗口中。我找到了一些方法来自己解决我的问题,使用带有可变参数模板的多重继承。再次感谢您
    猜你喜欢
    • 1970-01-01
    • 2017-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多