【问题标题】:What's a good way to store a small, fixed size, hierarchical set of static data?存储小型、固定大小、分层的静态数据集的好方法是什么?
【发布时间】:2021-09-06 09:25:36
【问题描述】:

我正在寻找一种方法来存储一个小的多维数据集,这些数据在编译时是已知的并且永远不会改变。此结构的目的是充当存储在单个命名空间中的全局常量,但可以全局访问而无需实例化对象

如果我们只需要一个级别的数据,有很多方法可以做到这一点。您可以将 enumclassstruct 与静态/常量变量一起使用:

class MidiEventTypes{
   public:
   static const char NOTE_OFF = 8;
   static const char NOTE_ON = 9;
   static const char KEY_AFTERTOUCH = 10;
   static const char CONTROL_CHANGE = 11;
   static const char PROGRAM_CHANGE = 12;
   static const char CHANNEL_AFTERTOUCH = 13;
   static const char PITCH_WHEEL_CHANGE = 14;
};

通过使用这个类和它的成员,我们可以轻松地比较程序中任何地方的数值变量:

char nTestValue = 8;
if(nTestValue == MidiEventTypes::NOTE_OFF){} // do something...

但是,如果我们想要存储的不仅仅是名称和值对怎么办?如果我们还想用 each 常量存储一些 extra 数据怎么办?在上面的示例中,假设我们还想存储每种事件类型必须读取的字节数。

下面是一些伪代码用法:

char nTestValue = 8;
if(nTestValue == MidiEventTypes::NOTE_OFF){
   std::cout << "We now need to read " << MidiEventTypes::NOTE_OFF::NUM_BYTES << " more bytes...." << std::endl;
}

我们也应该能够做这样的事情:

char nTestValue = 8;
// Get the number of read bytes required for a MIDI event with a type equal to the value of nTestValue.
char nBytesNeeded = MidiEventTypes::[nTestValue]::NUM_BYTES; 

或者:

char nTestValue = 8;    
char nBytesNeeded = MidiEventTypes::GetRequiredBytesByEventType(nTestValue);

和:

char nBytesNeeded = MidiEventTypes::GetRequiredBytesByEventType(NOTE_OFF);

这个问题不是关于如何让实例化 类做到这一点。我已经可以做到了。问题是关于如何存储和访问与常量相关/附加的“额外”常量(不变)数据。 (运行时不需要这个结构!)或者如何创建一个多维常量。看起来这可以用静态类来完成,但我尝试了下面代码的几种变体,每次编译器发现有不同的地方要抱怨:

static class MidiEventTypes{
   
   public:
   static const char NOTE_OFF = 8;
   static const char NOTE_ON = 9;
   static const char KEY_AFTERTOUCH = 10; // Contains Key Data
   static const char CONTROL_CHANGE = 11; // Also: Channel Mode Messages, when special controller ID is used.
   static const char PROGRAM_CHANGE = 12;
   static const char CHANNEL_AFTERTOUCH = 13;
   static const char PITCH_WHEEL_CHANGE = 14;
   
   // Store the number of bytes required to be read for each event type.
   static std::unordered_map<char, char> BytesRequired = {
      {MidiEventTypes::NOTE_OFF,2},
      {MidiEventTypes::NOTE_ON,2},
      {MidiEventTypes::KEY_AFTERTOUCH,2},
      {MidiEventTypes::CONTROL_CHANGE,2},
      {MidiEventTypes::PROGRAM_CHANGE,1},
      {MidiEventTypes::CHANNEL_AFTERTOUCH,1},
      {MidiEventTypes::PITCH_WHEEL_CHANGE,2},
   };
   
   static char GetBytesRequired(char Type){
      return MidiEventTypes::BytesRequired.at(Type);
   }
   
};

此特定示例不起作用,因为它不允许我创建 static unordered_map。如果我不制作unordered_mapstatic,那么它会编译但GetBytesRequired() 无法找到地图。如果我将GetBytesRequired() 设为非静态,它可以找到地图,但是如果没有MidiEventTypes 的实例,我就无法调用它,而且我不想要它的实例。

同样,这个问题不是关于如何修复编译错误,而是关于存储不只是键/值对的静态/常量数据的适当结构和设计模式。

这些是目标:

  • 数据和大小在编译时是已知的,永远不会改变。

  • 使用每个数据集的人类可读密钥访问一小组数据。关键应该 映射到特定的非线性整数。

  • 每个数据集都包含相同的成员数据集。 IE。每个MidiEventType 都有一个NumBytes 属性。

  • 可以使用命名键或函数访问子项。

  • 使用键,(或表示键值的变量),我们应该 能够读取与键的常量项关联的额外数据 指向,对额外数据使用另一个命名键。

  • 我们不需要实例化一个类来读取这些数据,因为 没有任何变化,并且不应有超过一份 数据集。

  • 事实上,除了包含指令之外,不需要任何东西来访问数据,因为它应该表现得像一个常量。

  • 我们在运行时不需要这个对象。目标是通过使用命名标签结构存储数据组,而不是到处使用(不明确的)整数文字,使代码更有条理,更易于阅读。

  • 它是一个常量,您可以深入了解...例如 JSON。

  • 理想情况下,不应该要求强制转换来使用常量的值。

  • 我们应该避免重复数据并且可能不同步的冗余列表。例如,一旦我们定义了 NOTE_ON = 9,文字 9 就不应出现在其他任何地方。应改为使用标签NOTE_ON,以便只能在一处更改值。

  • 这是一个笼统的问题,MIDI只是作为一个例子。

  • 常量应该可以有多个属性。

存储在编译时已知的小型、固定大小、分层(多维)静态数据集的最佳方法是什么?使用与常量相同的用例?

【问题讨论】:

  • 这主要是基于意见。不过,我会使用enum MidiEventType,它只是作为const std::unordered_map&lt;MidiEventType,MidiEventData&gt; 的键,而不是为数据保留单独的字段,而是全部在MidiEventData
  • 通过一些修改为我工作:ideone.com/Evm7Dg 除非您真的要抓取几个字节,否则我会将字节值设为 int。
  • 只有一个维度,有很多属性,没有很多维度。
  • 我认为典型的是一个普通数组。
  • 您说在运行时不需要此结构,但显示您正在从非constexpr 键查找属性的示例代码。您是否需要您绘制的运行时查找?还是您实际上总是使用文字常量键(NOTE_ON 等)?

标签: c++ containers


【解决方案1】:

这是我的看法,一个完整的 constexpr 编译时解决方案。 为了您的使用,还可以将 MIDI 内容放在头文件中,您就可以开始使用了。

带头文件https://www.onlinegdb.com/lGp7zMNB6

#include <iostream>
#include "const_string.h"
#include "const_map.h"

namespace midi
{
    using data_t = char;
    using string_t = const_string<32>; // 32 is big enough to hold strings in map

    namespace control
    {
        constexpr data_t NOTE_OFF = 8;
        constexpr data_t NOTE_ON = 9;
        constexpr data_t KEY_AFTERTOUCH = 10;
        constexpr data_t CONTROL_CHANGE = 11;
        constexpr data_t PROGRAM_CHANGE = 12;
        constexpr data_t CHANNEL_AFTERTOUCH = 13;
        constexpr data_t PITCH_WHEEL_CHANGE = 14;
    } /* namespace control */

    constexpr auto required_bytes = make_const_map<data_t, data_t>({
        {control::NOTE_OFF,2},
        {control::NOTE_ON,2},
        {control::KEY_AFTERTOUCH,2},
        {control::CONTROL_CHANGE,2},
        {control::PROGRAM_CHANGE,1},
        {control::CHANNEL_AFTERTOUCH,1},
        {control::PITCH_WHEEL_CHANGE,2}
    });

    constexpr auto str = make_const_map<data_t, string_t>({
        { control::NOTE_ON,"Note on" },
        { control::NOTE_OFF,"Note off" },
        { control::CONTROL_CHANGE, "Control change"},
        { control::CHANNEL_AFTERTOUCH, "Channel aftertouch"},
        { control::PITCH_WHEEL_CHANGE, "Pitch wheel change"}
    });

} /* namespace midi */

int main()
{
    static_assert(midi::control::NOTE_OFF == 8, "test failed");
    static_assert(midi::required_bytes[midi::control::NOTE_OFF] == 2, "test failed");
    static_assert(midi::required_bytes[13] == 1, "test failed");
    static_assert(midi::str[midi::control::NOTE_OFF] == "Note off", "test failed");

    return 0;
}

//接受后编辑:更简洁的语法

#include <iostream>
#include "const_string.h"
#include "const_map.h"

namespace midi_details
{
    using data_t = char;
    using string_t = const_string<32>;
}

constexpr midi_details::data_t MIDI_NOTE_OFF = 8;
constexpr midi_details::data_t MIDI_NOTE_ON = 9;
constexpr midi_details::data_t MIDI_KEY_AFTERTOUCH = 10;
constexpr midi_details::data_t MIDI_CONTROL_CHANGE = 11;
constexpr midi_details::data_t MIDI_PROGRAM_CHANGE = 12;
constexpr midi_details::data_t MIDI_CHANNEL_AFTERTOUCH = 13;
constexpr midi_details::data_t MIDI_PITCH_WHEEL_CHANGE = 14;

namespace midi_details
{
    constexpr auto required_bytes = make_const_map<data_t, data_t>({
        {MIDI_NOTE_OFF,2},
        {MIDI_NOTE_ON,2},
        {MIDI_KEY_AFTERTOUCH,2},
        {MIDI_CONTROL_CHANGE,2},
        {MIDI_PROGRAM_CHANGE,1},
        {MIDI_CHANNEL_AFTERTOUCH,1},
        {MIDI_PITCH_WHEEL_CHANGE,2}
        });

    constexpr auto str = make_const_map<data_t, string_t>({
            { MIDI_NOTE_ON,"Note on" },
            { MIDI_NOTE_OFF,"Note off" },
            { MIDI_CONTROL_CHANGE, "Control change"},
            { MIDI_CHANNEL_AFTERTOUCH, "Channel aftertouch"},
            { MIDI_PITCH_WHEEL_CHANGE, "Pitch wheel change"}
        });

    struct info_t
    {
        constexpr info_t(data_t r, string_t n) :
            required_bytes{ r },
            name{ n }
        {
        }

        data_t  required_bytes;
        string_t name;
    };

} /* namespace midi_details */

constexpr auto midi(midi_details::data_t value)
{
    return midi_details::info_t{ midi_details::required_bytes[value], midi_details::str[value] };
}

int main()
{
    static_assert(MIDI_NOTE_OFF == 8);
    static_assert(midi(MIDI_NOTE_OFF).required_bytes == 2, "test failed");
    static_assert(midi(MIDI_NOTE_OFF).name == "Note off", "test failed");

    return 0;
}

【讨论】:

  • 我喜欢这个,因为它保持干净和简单。但是我在哪里可以找到更多关于const_map.hmake_const_map 的信息?这是哪个图书馆的一部分?
  • 是的,这行得通,谢谢!我现在将通过它并确保我了解它是如何工作的。 :)
  • 如果您发现任何问题,请告诉我,很高兴澄清。它还没有达到完整的生产质量(并非所有类型都有效,也没有类型检查,只是丑陋的编译器错误)。但目前与其他 constexpr 类型配合得很好。
  • const_map 的第 72 行有点捷径。它之所以有效,是因为键值对的相等运算符只检查键部分。它稍微违反了我自己关于“没有技巧”的规则。
  • @Nick 哎呀我看到我没有为这个更新的语法添加正确的链接 static_assert(midi(MIDI_NOTE_OFF).required_bytes == 2, "test failed");,链接是onlinegdb.com/lGp7zMNB6
【解决方案2】:

比如:

struct MidiEventType
{
    char value;
    char byteRequired; // Store the number of bytes required to be read
};

struct MidiEventTypes{
   static constexpr MidiEventType NOTE_OFF { 8, 2};
   static constexpr MidiEventType NOTE_ON { 9, 2};
   static constexpr MidiEventType KEY_AFTERTOUCH { 10, 2};
   static constexpr MidiEventType CONTROL_CHANGE { 11, 2};
   static constexpr MidiEventType PROGRAM_CHANGE  { 12, 1};
   static constexpr MidiEventType CHANNEL_AFTERTOUCH { 13, 1};
   static constexpr MidiEventType PITCH_WHEEL_CHANGE { 14, 2};
};

【讨论】:

  • 我认为这需要某种类型的运算符重载,以便MidiEventTypes::NOTE_OFF 返回它的value 属性MidiEventType。否则你会得到类似could not convert 'MidiEventTypes::NOTE_OFF' from 'const MidiEventType' to 'int' 的东西,因为它不知道在比较运算符中使用value
  • @Nick:我会使用NOTE_OFF.value,但consexpr operator char() const { return value; } 应该可以替代您的语法。
  • 谢谢!所以这适用于比较值。但是当采用另一种方式时,如何使用表示值的 int 变量访问成员属性byteRequired?如果我们有int nEventType = 8;,那么我们需要使用该 EventType 值来获取数据,例如:int BytesRqd = MidiEventTypes::[nEventType].nBytesRequired;
  • NOTE_OFF.byteRequired?如果您想查找/查找,也可以使用static constexpr std::array&lt;MidiEventType , 7&gt; {NOTE_OFF, NOTE_ON, KEY_AFTERTOUCH, ..}
  • 这对我来说似乎是最好的解决方案。我们可以让事件更简洁like this。我们可以很容易地添加一些东西来以另一种方式访问​​value,但我不明白我们为什么要这样做。
【解决方案3】:

这是我对模板的看法。我使用的是int 而不是char,但您可以根据自己的需要进行更改。直播代码here

#include <iostream>

template <int V, int B>
struct MidiEventType
{
    static constexpr int value = V;

    static constexpr int bytes = B;

    constexpr operator int() const
    {
        return V;
    }
};

// dummy classes, used for accessing a given property from MidiEventType
// create as many as the number of properties in MidiEventType and specialize GetProperty for each
struct Value;
struct Bytes;

template <class T, class Property>
struct GetProperty;

template <class T>
struct GetProperty<T, Value>
{
    static constexpr auto property = T::value;
};

template <class T>
struct GetProperty<T, Bytes>
{
    static constexpr auto property = T::bytes;
};

struct MidiEventTypes
{
    static constexpr MidiEventType<8,2> NOTE_OFF{};
    static constexpr MidiEventType<9,2> NOTE_ON{};
    static constexpr MidiEventType<10,2> KEY_AFTERTOUCH{};
    static constexpr MidiEventType<11,2> CONTROL_CHANGE{};
    static constexpr MidiEventType<12,1> PROGRAM_CHANGE{};
    static constexpr MidiEventType<13,1> CHANNEL_AFTERTOUCH{};
    static constexpr MidiEventType<14,2> PITCH_WHEEL_CHANGE{};
    static constexpr MidiEventType<-1,-1> INVALID{};

    // perform the lookup
    template <class Property>
    static constexpr auto get(int key)
    {
        return get_impl<Property, decltype(NOTE_OFF), decltype(NOTE_ON),
                decltype (KEY_AFTERTOUCH), decltype (CONTROL_CHANGE),
                decltype (PROGRAM_CHANGE), decltype (CHANNEL_AFTERTOUCH),
                decltype (PITCH_WHEEL_CHANGE)>::call(key);
    }

private:

    // class to automate the construction of if/else branches when looking up the key
    // our template parameters here will be MidiEventType<X,Y>
    template <class Property, class T, class... Rest>
    struct get_impl
    {
        static constexpr auto call(int key)
        {
            if(T::value == key) return GetProperty<T, Property>::property;
            else return get_impl<Property, Rest...>::call(key);
        }
    };

    // specialization for a single class
    // if the key is not found then return whatever we've set for the INVALID type
    template <class Property, class T>
    struct get_impl<Property, T>
    {
        static constexpr auto call(int key)
        {
            if(T::value == key) return GetProperty<T, Property>::property;
            else return GetProperty<decltype(INVALID), Property>::property;
        }
    };
};

int main()
{
    std::cout << MidiEventTypes::CHANNEL_AFTERTOUCH.bytes << std::endl;
    std::cout << MidiEventTypes::get<Value>(MidiEventTypes::NOTE_OFF) << std::endl;
    std::cout << MidiEventTypes::get<Bytes>(MidiEventTypes::CHANNEL_AFTERTOUCH) << std::endl;
    std::cout << MidiEventTypes::get<Bytes>(42) << std::endl; // invalid key, return INVALID.bytes
}

【讨论】:

  • 这似乎是一个合适的解决方案,尽管我将get 函数和成员放在一个宏中以确保您不会意外错过一个。 +1 任一方式
【解决方案4】:

这里肯定有很多很好的聪明解决方案,尽管我觉得需要有人提供来代表简单的方法。只要您不需要一直使用方括号来查找元数据,您就可以在constexpr 函数中使用switch 语句。这是我的解决方案:

#include <iostream>

namespace MidiEvents {

struct MidiEventMetaData {
    int num_bytes;
    const char *str;
    uint32_t stuff;
};

enum MidiEventTypes {
   NOTE_OFF = 8,
   NOTE_ON = 9,
   KEY_AFTERTOUCH = 10,
   CONTROL_CHANGE = 11,
   PROGRAM_CHANGE = 12,
   CHANNEL_AFTERTOUCH = 13,
   PITCH_WHEEL_CHANGE = 14,
   OTHER = 17
};

constexpr MidiEventMetaData get(char event_type)
{
    switch (event_type) {
    default:
        break;
    case NOTE_OFF:
        return { 1, "note off", 7 }; 
    case NOTE_ON:
        return { 1, "note on", 20 }; 
    case KEY_AFTERTOUCH:
        return { 2, "aftertouch", 100 };
    }
    return { 0, "unknown", 0 };
}

constexpr char GetRequiredBytesByEventType(char event_type)
{
    return get(event_type).num_bytes;
}

constexpr const char *GetEventNameByType(char event_type)
{
    return get(event_type).str;
}

} // namespace MidiEvents

int main(int argc, char **argv)
{
    char num_bytes = MidiEvents::GetRequiredBytesByEventType(MidiEvents::KEY_AFTERTOUCH);
    const char * const name = MidiEvents::GetEventNameByType(MidiEvents::KEY_AFTERTOUCH);
    std::cout << "name = " << name << "\n"; 
    std::cout << "num_bytes = " << (int)num_bytes << "\n";
    return 0;
}

需要注意的是,实际上,在您使用-O2 构建之前,编译器不会将所有这些都分解为实际常量。在godbolt 上查看。你可以清楚地看到主函数只是调用cout,传入常量值。如果您删除-O2,情况将不再如此。

这里的优点是这段代码非常接近您在最简单的场景中编写的代码。几乎每个人都可以理解,需要绝对最小的非易失性存储,并且对事件值排序等没有限制。

【讨论】:

  • +1 用于发布简单的答案。我喜欢它使用标准库。正如我在另一条评论中所说,我觉得很快就会在标准库中出现某种 constexpr JSON 对象,因为似乎有人对类似的东西感兴趣,并提出了一些建议。
【解决方案5】:

解决办法:

首先我们为类型创建一个通用映射器map_t。为此,我们要求每个类型(映射到)都有一个名为 keystatic constexpr 值:

template <auto, auto, typename>
struct type_if_equal {};

template <auto k, typename T>
struct type_if_equal <k, k, T> : T {};

template <auto k, typename ... Ts>
struct map_t : type_if_equal<k, Ts::key, Ts>... {};

对于 OP 的问题,我们将数据放入 struct 及其关联的 Event 作为 key。最后我们用using把它包装成用户友好的东西:

struct Midi {

    enum class Event : char {
        NOTE_OFF = 8,
        NOTE_ON,    // +1 till specified
        KEY_AFTERTOUCH,
        CONTROL_CHANGE,
        PROGRAM_CHANGE,
        CHANNEL_AFTERTOUCH,
        PITCH_WHEEL_CHANGE
    };
    
private:
    // D = Data (shortened for re-use in mapping)
    template <Event e, int bytes /* other data */ >
    struct D {
        constexpr static Event key = e;
        constexpr static int BytesRequired = bytes;
        /* store other data here */
    };
    
public:
    
    template <Event e>
    using Info = map_t<e,
        D<Event::NOTE_OFF, 2>,
        D<Event::NOTE_ON, 2>,
        D<Event::KEY_AFTERTOUCH, 2>,
        D<Event::CONTROL_CHANGE, 2>,
        D<Event::PROGRAM_CHANGE, 1>,
        D<Event::CHANNEL_AFTERTOUCH, 1>,
        D<Event::PITCH_WHEEL_CHANGE, 2>>;
};

演示:

我们实际上最终得到了一个名为Info 的类型“数组”,它接受任何Event 类型并为我们提供适当的Data 类型(以及我们关心的static 数据)。


解的一般性:

对于给出的特定示例问题,这里的其他一些答案要好得多(更简单但仍然有效)。但是,OP 要求提供比示例问题更通用的内容。

我认为这里的想法是我们可能希望使用元编程 (MP) 来推断 event 值,然后需要一些实际上将event 作为变量的东西,而不仅仅是一个名称(我认为这是 OP 感兴趣的功能)。我们可以让我们的 MP 依赖于数据收集,但这有更多的耦合 - 如果我们不编写 MP 代码怎么办?

在这个答案中,我假设 Key 类型 不能 被更改以使其能够很好地用于映射。我也不认为键会有很好的顺序来进行简单的映射:对于 OP,我们可以只映射 array[event - 8],但这不是通用解决方案。

这是针对小众问题的小众解决方案。请注意,我列出了两次 Event 元素 - 不是必要的 - 而是因为我展示了键定义和映射的分离。


解释:

直观地说,数组似乎是最简单的选择,但我想避免生成索引映射。相反,我们使用编译器的本机映射。原来我的回答是这样的:

template <int b /* other data */ >
struct Data {
    constexpr static int BytesRequired = b;
    /* store other data here */
};

template <Event>
struct Info {};

// Specify mappings:
template <>
struct Info <Event::NOTE_OFF> : Data<2> {};

template <>
struct Info <Event::NOTE_ON> : Data<2> {};

template <>
struct Info <Event::KEY_AFTERTOUCH> : Data<2> {};

// ...

...但是我想避免重复的样式,所以我使用pack expansion 的“条件”多重继承,它有效地生成了一个类似上面的列表。我第一次看到这个绝妙的技巧here。起初它可能看起来很奇怪,但它在元编程中很常见,而且它对编译器的效率惊人(比递归好得多),当然也没有运行时开销。

【讨论】:

  • "这里的想法是,我们可能想要使用元编程来推断事件值,然后访问适当的数据,我们需要将事件实际作为变量,而不仅仅是一个名字……” 没错。 +1 在这里有很多很好的信息。我觉得我正在尝试做的事情在未来版本的 C++ 中将是微不足道的,因为似乎(从观看 CPPcon 谈论“constexpr all the things”)对静态数据结构感兴趣,但@987654345 @library 需要一些调整和开发才能使其正常工作。
【解决方案6】:

只需编写constexpr 代码即可访问它。这是我非常混乱的例子:

#include <array>
#include <cstddef>
#include <stdexcept>
#include <iostream>

enum class MidiEvents {
   NOTE_OFF,
   NOTE_ON,
   KEY_AFTERTOUCH,
   CONTROL_CHANGE,
   PROGRAM_CHANGE,
   CHANNEL_AFTERTOUCH,
   PITCH_WHEEL_CHANGE,
   MIDIEVENTS_CNT,
};
constexpr bool operator==(const MidiEvents& a, const char& b) {
    return b == static_cast<char>(a);
}

struct MidiEventType {
    char value;
    char num_bytes; // Store the number of bytes required to be read
    constexpr bool operator==(const char& other) const {
        return value == other;        
    }
    constexpr bool operator==(const MidiEvents& other) const {
        return static_cast<char>(other) == value;
    }
};
constexpr bool operator==(const char& a, const MidiEventType& b) {
    return b == a;
}

struct MidiEventTypes {
    static constexpr std::array<
        MidiEventType, static_cast<size_t>(MidiEvents::MIDIEVENTS_CNT)
    > _data{{
        [static_cast<char>(MidiEvents::NOTE_OFF)] = {8, 2},
        [static_cast<char>(MidiEvents::NOTE_ON)] = {9, 2},
        /* etc.... */
    }};
    static constexpr auto get(char m) {
        for (auto&& i : _data) {
            if (i.value == m) {
                return i;
            }
        }
    }
    static constexpr auto get(MidiEvents m) {
        return _data[static_cast<char>(m)];
    }
    static constexpr auto GetRequiredBytesByEventType(char m) {
        return get(m).num_bytes;
    }
    static constexpr auto GetRequiredBytesByEventType(MidiEvents m) {
        return get(m).num_bytes;
    }
    static constexpr auto NOTE_OFF = _data[static_cast<char>(MidiEvents::NOTE_OFF)];
    static constexpr auto NOTE_ON = _data[static_cast<char>(MidiEvents::NOTE_ON)];
};

这样:

int main() {
    // Here's some pseudo code usage:
    constexpr char nTestValue = 8;
    if (nTestValue == MidiEventTypes::NOTE_OFF) {
        std::cout << "We now need to read " << MidiEventTypes::NOTE_OFF.num_bytes << " more bytes...." << std::endl;
    }
    // We should also be able to do something like this:
    // Get the number of read bytes required for a MIDI event with a type equal to the value of nTestValue.
    constexpr char nBytesNeeded = MidiEventTypes::get(nTestValue).num_bytes; 
    // Or alternatively:
    constexpr char nBytesNeeded2 = MidiEventTypes::GetRequiredBytesByEventType(nTestValue);
    // and:
    constexpr char nBytesNeeded3 = MidiEventTypes::GetRequiredBytesByEventType(MidiEvents::NOTE_OFF);
}

它不会让我创建静态 unordered_map

是的,unordered_map 分配内存并对其中的内容进行排序。只需使用一个普通数组,不要在任何地方分配内存——这在编译时就已经知道了。

【讨论】:

    【解决方案7】:

    除了我提出的使用模板的解决方案之外,这里还有一个基于 Jarod42 回答的更简单的解决方案。我们将使用一个数组并利用键是连续的(8->14)这一事实。查找密钥时,我们只需减去 8;这样数组可以恰好容纳 7 个元素。这种方法比模板更简单,但只能在要查找的值连续时使用。直播代码here

    #include <iostream>
    #include <array>
    
    struct MidiEventType
    {
        char value;
        char bytes;
        
        constexpr operator char() const { return value; }
    };
    
    struct MidiEventTypes
    {
        static constexpr MidiEventType NOTE_OFF { 8, 2};
        static constexpr MidiEventType NOTE_ON { 9, 2};
        static constexpr MidiEventType KEY_AFTERTOUCH { 10, 2};
        static constexpr MidiEventType CONTROL_CHANGE { 11, 2};
        static constexpr MidiEventType PROGRAM_CHANGE  { 12, 1};
        static constexpr MidiEventType CHANNEL_AFTERTOUCH { 13, 1};
        static constexpr MidiEventType PITCH_WHEEL_CHANGE { 14, 2};
        
        static constexpr std::array<MidiEventType, 7> events{NOTE_OFF, NOTE_ON, KEY_AFTERTOUCH, CONTROL_CHANGE, PROGRAM_CHANGE, CHANNEL_AFTERTOUCH, PITCH_WHEEL_CHANGE};
        
        static constexpr auto get(char key)
        {
            // offset the key by 8 and then look into the array
            return events[(std::size_t)key - 8];
        }
    };
    
    int main()
    {
        MidiEventTypes::get(MidiEventTypes::CONTROL_CHANGE).bytes;
        MidiEventTypes::get(MidiEventTypes::PROGRAM_CHANGE).bytes;
    }
    

    【讨论】:

    • 我考虑过做 -8 的事情,但出于两个原因避免了它:这似乎是一个“神奇的数字”,可能会让另一个程序员有些困惑(意思是我,六个月后) 并且它只在这种特定情况下有效,而我试图找到一个更通用的答案,可以在需要这种类型的结构的任何时候应用(即不仅仅是 MIDI 事件)。我认为您的模板答案更好,所以我投给了那个。 :)
    【解决方案8】:

    我看到了两个可行的解决方案:

    1. 如果您的数据高度不均匀且层次结构可能更深,则使用 JSON 将有效并提供足够的灵活性,例如使用Niels Lohmann's C++ json library。您会损失一些性能和类型安全性,但您可以非常灵活地选择如何构建数据以及将存在哪些类型。

    2. 如果性能和类型安全更重要,您可以使用有限但性能更高的代码,例如:

    #include <iostream>
    #include <map>
    #include <vector>
    
    enum class Events { NOTE_OFF, PROGRAM_CHANGE };
    
    const std::map<Events, const int> bytes_per_events = {
        {Events::NOTE_OFF, 2},
        {Events::PROGRAM_CHANGE, 1}
        // ...
    };
    
    int main()
    {
        std::cout << bytes_per_events.at(Events::NOTE_OFF) << " "
                  << bytes_per_events.at(Events::PROGRAM_CHANGE) << "\n";
        return 0;
    }
    
    

    您可以使用类而不是整数或使用不同的容器,具体取决于需要什么。

    【讨论】:

      猜你喜欢
      • 2013-11-19
      • 1970-01-01
      • 2013-05-30
      • 1970-01-01
      • 1970-01-01
      • 2020-09-17
      • 1970-01-01
      • 1970-01-01
      • 2019-05-03
      相关资源
      最近更新 更多