【问题标题】:Initialize array of compile time defined size as constant expression初始化编译时间定义大小的数组作为常量表达式
【发布时间】:2019-10-22 04:15:53
【问题描述】:

我有一个必须分配一次的字符串数组,并且它们的底层 c_str 必须在整个程序期间保持有效。

有一些 API 提供有关某些任意数据类型的信息。可能看起来像这样:

// Defined outside my code
#define NUMBER_OF_TYPES 23
const char* getTypeSuffix(int index);

getTypeSuffix 不是 constexpr,因此它必须至少部分在运行时工作。

必须提供的界面:

// Returned pointer must statically allocated (not on stack, not malloc)
const char* getReadableTypeName(int type);

现在我的数组应该有以下类型:

std::string typeNames[NUMBER_OF_TYPES];

出于我的目的,它将在包装类中初始化,就在构造函数中:

class MyNames
{
  MyNames()
  {
    for (int i = 0; i < NUMBER_OF_TYPES; ++i)
    {
      names[i] = std::string("Type ") + getTypeSuffix(i);
    }
  }

  const char* operator[](int type) { return _names[(int)type].c_str(); }

private:
  std::string _names[NUMBER_OF_TYPES];
};

然后以单例方式使用,例如:

const char* getReadableTypeName(int type) 
{
  static MyNames names;
  return names[type];
}

现在我要改进的是我可以看到构造函数中的for循环可以这样替换:

 MyNames() : _names{std::string("Type ") + getTypeSuffix(0), std::string("Type ") + getTypeSuffix(1), ... , std::string("Type ") + getTypeSuffix(NUMBER_OF_TYPES-1)}
 {}

显然是伪代码,但你明白了——数组可以直接初始化,构造函数没有主体,这很整洁。这也意味着数组成员_names可以是const,进一步强制了这个助手类的正确使用。

我很确定在编译时用表达式填充数组还有很多其他用途,而不是循环。我什至怀疑这是在03 期间无论如何都会发生的事情。

有没有办法编写具有灵活长度并由表达式定义的 C++11 样式数组初始化列表?另一个简单的例子是:

constexpr int numberCount = 10;
std::string numbers[] = {std::to_string(1), std::to_string(2), ... , std::to_string(numberCount)};

同样,一个表达式而不是一个循环。

我问这个问题不是因为我试图大幅提高性能,而是因为我想了解 C++14 及更高版本的新的、简洁的特性。

【问题讨论】:

  • 老实说,我没有看到您的问题,该数组是 MyNames 类的实现细节。唯一的公共访问器是提供恒定输出的操作员。 (您仍然可以将方法设为 const)您想要什么额外的优势?
  • 为什么不使用std::array 而不是C 风格的数组?

标签: c++ arrays c++11 c++14


【解决方案1】:

使用 std::array 代替 C 数组,然后您可以编写函数来返回 std::array,然后您的成员可以是 const

std::array<std::string, NUMBER_OF_TYPES> build_names()
{
    std::array<std::string, NUMBER_OF_TYPES> names;
    for (int i = 0; i < NUMBER_OF_TYPES; ++i)
    {
          names[i] = std::string("Type ") + getTypeSuffix(i);
    }
    return names;
}


class MyNames
{
  MyNames() : _names(build_names()) {}
  const char* operator[](int type) const { return _names[(int)type].c_str(); }

private:
  const std::array<std::string, NUMBER_OF_TYPES> _names;
};

现在你有了std::array,你可以使用可变参数模板而不是循环,比如(std::index_sequence 的东西是 C++14,但可以在 C++11 中实现):

template <std::size_t ... Is> 
std::array<std::string, sizeof...(Is)> build_names(std::index_sequence<Is...>)
{
     return {{ std::string("Type ") + getTypeSuffix(i) }};
}

然后调用它:

MyNames() : _names(build_names(std::make_index_sequence<NUMBER_OF_TYPES>())) {}

【讨论】:

  • 我的意思是很酷,但是为什么写我想要了解新功能的文章会提示使用我已经知道并且仍然是旧 for 循环的方法的答案?
  • @TomášZato 可能是因为旧的东西本身并不意味着它很糟糕?
  • @TomášZato 你可以让build_names() 返回auto。够新吗?
【解决方案2】:

您可以使用初始化函数:

std::array<std::string, NUMBER_OF_TYPES> initializeNames()
{
    std::array<std::string, NUMBER_OF_TYPES> names;
    for (int i = 0; i < NUMBER_OF_TYPES; ++i) {
        names[i] = std::string("Type ") + getTypeSuffix(i);
    }
    return names;
}

const char* getReadableTypeName(int type) 
{
  static auto const names = initializeNames();
  return names[type].c_str();
}

可以是立即调用的 lambda:

static auto const names = []{
    std::array<std::string, NUMBER_OF_TYPES> names;
    // ...
    return names;
}();

或者您真的需要array 要求吗?反正我们在做字符串所以我不明白,那么你可以使用 range-v3:

char const* getReadableTypeName(int type) {
    static auto const names =
        view::iota(0, NUMBER_OF_TYPES)
        | view::transform([](int i){ return "Type "s + getTypeSuffix(i); })
        | ranges::to<std::vector>();
    return names[type].c_str():
}

【讨论】:

  • 或者使用立即调用的 lambda 进行初始化!
  • 我已经通过遵循构造函数来做到这一点。但是,创建一个成员 const 是不同的——这是一种防止进一步分配的非常卑鄙的方式。
  • @TomášZato 当然,但是与引入函数相比,为此引入类型似乎很尴尬。我不知道这有什么“意思”。
  • @Barry 我的意思是我有意通过使其成为唯一成员 const 来强制这种类型是 const。您的解决方案是我所拥有的很好的替代方案,但实际上不是我想要的。
【解决方案3】:

您可以使用std::make_integer_sequence 和 C++14 中的委托构造函数(std::make_integer_sequence 的实现存在于 C++11 中,因此这不是 C++14 特定的)来获取整数模板参数包

#include <string>
#include <utility>

#define NUMBER_OF_TYPES 23

const char* getTypeSuffix(int index);

class MyNames
{
  MyNames() : MyNames(std::make_integer_sequence<int, NUMBER_OF_TYPES>{}) {}

  template<int... Indices>
  MyNames(std::integer_sequence<int, Indices...>) : _names{ (std::string("Type ") + getTypeSuffix(Indices))... } {}

  const char* operator[](int type) { return _names[(int)type].c_str(); }

private:
  const std::string _names[NUMBER_OF_TYPES];
};

这意味着没有默认构造字符串。

【讨论】:

    【解决方案4】:

    既然你渴望使用新功能,让我们使用range-v3(即将成为 C++2a 中的ranges 库)编写一些非常短的代码:

    const char* getReadableTypeName(int type) 
    {
        static const std::vector<std::string> names =
            view::ints(0, 23) | view::transform([](int i) {
                return "Type " + std::to_string(i);
            });
        return names[type].c_str();
    }
    

    https://godbolt.org/z/UVoENh

    【讨论】:

      猜你喜欢
      • 2019-10-15
      • 1970-01-01
      • 2010-09-14
      • 1970-01-01
      • 1970-01-01
      • 2012-08-03
      • 1970-01-01
      • 1970-01-01
      • 2015-01-13
      相关资源
      最近更新 更多