简体   繁体   English

如何在 C++ 编译时设计具有可切换成员变量的模板类?

[英]How to design a template class with switchable member variables at compile-time in C++?

| | ■ Probelm definition____________________ ■ 问题定义____________________

I'm trying to design a super-flexible, but memory efficient module satisfying below properties.我正在尝试设计一个超级灵活但内存高效的模块,满足以下属性。

  1. It can switch OFF unnecessary member variables depending on the situation.它可以根据情况关闭不必要的成员变量
  2. Which variables it will own are determined at the compile-time .它将拥有哪些变量是在编译时确定

I somehow made one which determines its member list " before " the compile-time , using macros and enumerator flags .我以某种方式使用枚举器标志制作了一个在编译时“之前确定其成员列表的列表。 see below :见下文 :

▼ TraitSwitch.h ▼ 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 ▼ 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 ▼ 三角形.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();
    ...
}

then, C++ IntelliSense on the object may show : this然后,对象上的C++ IntelliSense可能会显示: this

▼ main.cpp ▼ 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;
    ...
}

Firstly Triangle.h looks quite ugly, and even if it looks good, this method determines class members " before" the compile-time , anyway.首先Triangle.h看起来很丑,即使它看起来不错,无论如何,这个方法在编译时“之前”确定类成员。


| | ■ Question summary____________________ ■ 问题摘要____________________

"How to design a template class with switchable members , which are determined at the compile-time ." “如何设计具有可切换成员的模板类,这些成员在编译时确定。”

Here's the very thing what I want :这就是我想要的东西:

▼ main.cpp ▼ 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;
    ...
}

I guess using templates combined with macros (with Tag-dispatching method, maybe?) will do exactly what I want, but have no any clear idea.我想使用模板与宏相结合(使用标签调度方法,也许?)将完全符合我的要求,但没有任何明确的想法。

After a few days of distress, I found a way.经过几天的苦恼,我找到了办法。 So I'll answer my question myself.所以我会自己回答我的问题。

The soultion was tooooo simple.灵魂太简单了。 Just use multiple inheritances with variadic templates .只需对可变参数模板使用多重继承 See below:见下文:

▼TriangleTraits.h ▼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 ▼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;
...
}

Cannot use preprocessor to parse template parameter, preprocesssor does not know of C++.不能使用预处理器来解析模板参数,预处理器不知道 C++。

But there's std::conditional and there's Empty Base optimization.但是有std::conditional和 Empty Base 优化。

So you can have bases with data class parts, and std::conditional to select between them and 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;
}

And with later C++ standards, there's std::coditional_t and [[no_unique_address]] :在后来的 C++ 标准中,有std::coditional_t [[no_unique_address]][[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;
}

But [[no_unique_address]] requires C++20, and without [[no_unique_address]] empty member will use some space.但是[[no_unique_address]]需要 C++20,没有[[no_unique_address]]空成员会占用一些空间。 Looks like it does not work with Visual C++, even with 2019 preview.看起来它不适用于 Visual C++,即使是 2019 预览版也是如此。 I've posted feebdack .我已经发布了反馈

With earlier C++, when you don't have std::conditional or boost::conditional , you can easily implement your own, it is just a template with true and false specialization.使用早期的 C++,当您没有std::conditionalboost::conditional ,您可以轻松实现自己的,它只是一个具有 true 和 false 特化的模板。 See "possible implementation" on cppreference page请参阅cppreference 页面上的“可能的实现”

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM