【问题标题】:Can a C++ pointer point to a static member array of string literals?C++ 指针可以指向字符串文字的静态成员数组吗?
【发布时间】:2019-02-14 21:35:56
【问题描述】:

在 Visual Studio 2017 中一切正常,但在 GCC (6.5.0) 中出现链接器错误。

这里有一些示例代码可以解决我的问题:

#include <iostream>

struct Foo{
    static constexpr const char* s[] = {"one","two","three"};
};

int main(){
    std::cout << Foo::s[0] << std::endl;  //this works in both compilers
    const char* const* str_ptr = nullptr;
    str_ptr = Foo::s;                     //LINKER ERROR in GCC; works in VS
    std::cout << str_ptr[1] << std::endl; //works in VS
    return 0;
}

在 GCC 中,我得到 undefined reference to 'Foo::s'。我需要Foo::s 的初始化以留在结构的声明中,这就是我使用constexpr 的原因。有没有办法动态引用Foo::s,即使用指针?

更多背景信息

我现在将解释为什么我想这样做。我正在开发嵌入式软件来控制设备的配置。该软件加载包含参数名称及其值的配置文件。正在配置的参数集在编译时确定,但需要模块化,以便随着设备的开发继续添加新参数和扩展它们。换句话说,它们的定义需要在代码库中的一个地方。

我的实际代码库有数千行,可以在 Visual Studio 中运行,但这里有一个简化的玩具示例:

#include <iostream>
#include <string>
#include <vector>

//A struct for an arbitrary parameter
struct Parameter {
    std::string paramName;
    int max_value;
    int value;
    const char* const* str_ptr = nullptr;
};

//Structure of parameters - MUST BE DEFINED IN ONE PLACE
struct Param_FavoriteIceCream {
    static constexpr const char* n = "FavoriteIceCream";
    enum { vanilla, chocolate, strawberry, NUM_MAX };
    static constexpr const char* s[] = { "vanilla","chocolate","strawberry" };
};
struct Param_FavoriteFruit {
    static constexpr const char* n = "FavoriteFruit";
    enum { apple, banana, grape, mango, peach, NUM_MAX };
    static constexpr const char* s[] = { "apple","banana","grape","mango","peach" };
};

int main() {
    //Set of parameters - determined at compile-time
    std::vector<Parameter> params;
    params.resize(2);

    //Configure these parameters objects - determined at compile-time
    params[0].paramName = Param_FavoriteIceCream::n;
    params[0].max_value = Param_FavoriteIceCream::NUM_MAX;
    params[0].str_ptr = Param_FavoriteIceCream::s; //!!!! LINKER ERROR IN GCC !!!!!!

    params[1].paramName = Param_FavoriteFruit::n;
    params[1].max_value = Param_FavoriteFruit::NUM_MAX;
    params[1].str_ptr = Param_FavoriteFruit::s; //!!!! LINKER ERROR IN GCC !!!!!!

    //Set values by parsing files - determined at run-time
    std::string param_string = "FavoriteFruit"; //this would be loaded from a file
    std::string param_value = "grape"; //this would be loaded from a file
    for (size_t i = 0; i < params.size(); i++) {
        for (size_t j = 0; j < params[i].max_value; j++) {
            if (params[i].paramName == param_string
            && params[i].str_ptr[j] == param_value) {
                params[i].value = j;
                break;
            }
        }
    }
    return 0;
}

如您所见,其中涉及枚举和字符串数组,它们需要匹配,因此出于维护目的,我需要将它们保存在同一个位置。此外,由于此代码已经编写好并将在 Windows 和 Linux 环境中使用,因此修复越小越好。我宁愿不必为了在 Linux 中编译而重写数千行代码。谢谢!

【问题讨论】:

  • 这可能与您如何定义静态成员有关,而不仅仅是声明它。如果你添加inline 会发生什么:inline static constexpr const char* s[] = {"one","two","three"};?
  • @alterigel 从 C++17 开始,static constexpr 类成员变量隐含为inline
  • OP,如果你使用-std=c++17,它可以工作吗?
  • 很确定这是一个错误。 gcc 7.1+ 使用 c++17 编译良好。
  • 如果您无法获得 C++17 支持,解决问题的一种方法是将静态数据成员替换为返回所需常量的静态成员函数(在类定义中定义) .类定义中定义的函数是隐式内联的(自 C++98 以来一直如此)。

标签: c++ gcc constexpr string-literals


【解决方案1】:

该程序在 C++17 中有效。该程序在 C++14 或更早的标准中无效。 GCC 6.5.0 的默认标准模式是 C++14。

要使程序符合 C++14,您必须定义静态成员(恰好在一个翻译单元中)。从 C++17 开始,constexpr 声明隐含地是一个内联变量定义,因此不需要单独定义。

解决方案 1:升级您的编译器并使用具有内联变量的 C++17 标准(或更高版本,如果您来自未来)。内联变量自 GCC 7 起已实现。

解决方案 2:在一个翻译单元中定义类定义之外的变量(初始化保留在声明中)。

【讨论】:

    【解决方案2】:

    看起来您没有使用 C++17,而在 C++17 之前,这是未定义的行为,我非常不喜欢这种行为。你对你的s没有定义,但你是ODR-using它,这意味着你需要有一个定义。

    要定义它,你必须在 .cpp 文件中定义它。

    在 C++17 中,这将是有效的代码。我对 MSVC 不熟悉,所以我不确定为什么它在那里可以正常工作 - 无论是它被编译为 C++17,还是因为它只是未定义行为的不同表现形式。

    【讨论】:

    • 自 C++17 以来格式良好,因为变量是 implicitly inline。 (不是我的反对票。)
    • @HolyBlackCat 很明显,OP 没有使用 C++17。可能我也应该将它添加到答案中。
    • 来自cppreference:“如果 LiteralType 的静态数据成员声明为constexpr,则必须使用初始化程序对其进行初始化,其中每个表达式都是常量表达式,就在类定义中。” (并且字符串文字数组满足 LiteralType)。也不是我的反对意见,但我希望这会有所帮助
    • @alterigel 我看不出这句话与我的回答有何矛盾。
    • 对于那些对使用 ODR 感到好奇的人,What does it mean to “ODR-use” something?
    【解决方案3】:

    对于 C++98、C++11 和 C++14,您需要明确告诉编译器 Foo::s 的初始化所在的位置(见下文),然后就可以开始了。

    struct Foo{
        static const char* s[];
    };
    const char* Foo::s[] = {"one","two","three"};
    

    正如其中一个 cmets 中所解释的,您的初始化在 C++17 中是可以的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-28
      • 1970-01-01
      • 2018-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多