从字符串文字初始化 char* 向量的好方法是什么?

What is a good way of initializing a vector of char* from string literals?

提问人:bers 提问时间:10/26/2023 更新时间:10/26/2023 访问量:102

问:

在 C++11 之前,人们能够编写以下内容:

#include <vector>
using namespace std;

int main_under_test(int argc, char* argv[]) {
    return 0;
}

int main() {
    vector<char*> args {"--foo", "--bar"};

    return main_under_test(args.size(), args.data());
}

但是,不再可能将文本值分配给非 - 值。constchar*

幸运的是,从 C++11 开始,我们保证它是以 null 结尾的,因此我们可以执行以下操作:std::string::data()

#include <string>
#include <vector>
using namespace std;

int main_under_test(int argc, char* argv[]) {
    return 0;
}

int main() {
    vector<string> strArgs {"--foo", "--bar"};
    vector<char*> args;
    for (auto & strArg : strArgs) {
        args.push_back(strArg.data());
    }

    return main_under_test(args.size(), args.data());
}

然而,虽然这有效,但它看起来并不好。有没有更好的方法来初始化非 from 文字的(或数组)?vectorconstchar*

C++ 字符串 字符 文本

评论

2赞 463035818_is_not_an_ai 10/26/2023
请定义“更好”。为什么不使用?std::vector<std::string>
2赞 HolyBlackCat 10/26/2023
@463035818_is_not_an_ai 因为他们需要调用一个函数 接受 ,正如示例所暗示的那样。(int, char**)
0赞 user12002570 10/26/2023
也可能 codereview.stackexchange.com
3赞 Aconcagua 10/26/2023
However, assigning a literal value to a non-const char* is not possible any longer.– 实际上,无论如何都不应该这样做。C语言的这个遗留功能,现在被放弃了,它是一个漏洞,它引入了用于修改字符串文字的未定义行为。
1赞 bers 10/26/2023
@463035818_is_not_an_ai我对 不感兴趣,所以更好的解决方案将避免使用 ,例如。更好的解决方案也可能更短。(对于不再起作用的原始代码,两者都是正确的。vector<string>#include <string>

答:

2赞 HolyBlackCat 10/26/2023 #1

在 C++23 中:

std::vector<std::string> strings = {"--foo", "--bar"};
auto ptrs = strings
    | std::views::transform([](auto &s){return s.data();})
    | std::ranges::to<std::vector>();

Libstdc++ 还没有,但这适用于 libc++ 和 MSVC STL。ranges::to

可悲的是,我们不能直接传递给 ,因为是超载的。&std::string::datatransformdata()

评论

1赞 Konrad Rudolph 10/26/2023
您可以将其转换为所需的类型来选择特定的重载。然而,结果并不漂亮......().(当然,与反对票无关)static_caststd::views::transform(static_cast<char* (std::string::*)() noexcept>(&std::string::data))
0赞 HolyBlackCat 10/26/2023
@KonradRudolph嗯,想了想,但决定我更喜欢lambda。
0赞 Konrad Rudolph 10/26/2023
是的,当然 — lambda 的可读性要强得多。
1赞 Aconcagua 10/26/2023 #2

假设将来会有什么东西取代当前函数,那么对我来说,它似乎更适合简单地使用 shell 脚本 (linux) 或批处理文件 (windows) – 您可以在其中动态修改参数而无需重新编译 (!) – 并且现在只需使函数成为您的真正函数(所以实际上是一个 XY 问题......main_under_testmainmain_under_testmain

不过,如果您坚持使用当前的方法,那么您可以使用这些指针来初始化数组,而不是创建指向字符串文字的指针:

char raw[][128] = { "--foo", "--bar" };

好了,现在我们有一个二维数组——它不能只是转换为指针数组,所以在这种情况下,我们需要自己在代码中做到这一点,但只需要编写一次:

int main(int argc, char* argv[])
{
    char raw[][128] = { "--foo", "--bar" }; // arbitrarily modifiable...

    char* args[std::size(raw) + 2] = { *argv }; // the executable name (!)
    char** a = args;
    for(auto& r : raw)
    {
        *++a = r;
    }
    *++a = nullptr; // argv is always null-terminated as well!

    return main_under_test(std::size(args) + 1, args);
}

在godbolt上演示。

甚至可以在旧的 C++ 标准上工作(除了必须用 C++ 之前的旧技巧替换11 ...std::sizesizeof

另一种选择是简单地抛弃恒常性(),但随后你依赖于严格承诺不修改参数,否则会产生未定义的行为,因此这种方法不太值得推荐。{ const_cast<char*>("literals") }main_under_test

评论

0赞 bers 10/26/2023
可以看作是另一个可执行文件的功能,我通过链接到其文件(我猜是 MSVC 语言)来测试它。我喜欢在 Visual Studio 中调用我的测试并附加调试器的好处 - 我想,在启动子进程时会失去舒适感。main_under_testmain.obj
0赞 Aconcagua 10/27/2023
@bers 好吧,任何哪怕只有一半的 IDE 都应该附带为正在调试的应用程序提供命令行参数的方法!事实上,即使是 MSVS 也这样做了——可能是更好的选择......
0赞 bers 10/27/2023
阿空加瓜当然。但是,任何半途而废的 IDE 也有一个测试框架,您可以从附加到测试框架的调试器开始。为什么我要将每个失败测试的命令行参数复制到项目属性中,只是为了重新启动附加到失败测试启动的进程的调试器?
0赞 Aconcagua 10/27/2023
@bers 嗯,这就是到目前为止一直缺乏的所有信息......因此,您可以为每个测试创建专用的可执行文件,以便在失败时可以直接调试它们。当然,缺点是需要根据是否在测试中修改您的实际源代码(->在测试时重命名函数)。可能更大的缺点是您无法即时修改参数(需要重新编译)。让测试框架生成这样的批处理文件可能是一个有趣的想法,尽管不知道你的框架有多灵活,以及这是否可行......main
0赞 bers 10/28/2023
这是Microsoft的C++ VS Test框架:它构建dll文件,而不是可执行文件,不,我不必重命名任何主函数进行测试。除此之外,我想我宁愿不在评论中详细介绍测试框架的细节,以回答有关字符串文字的问题...... :)
2赞 James Kanze 10/26/2023 #3

我认为规范的解决方案是使用:

std::vector<char*> args { const_cast<char*>( "--foo" ),
                          const_cast<char*>( "--bar" ) }

的主要用途是与旧接口兼容,这些接口不会在应该使用的地方使用。const_castconst

请注意,如果实际尝试修改 args 中的 s,这将不起作用。如果它是实际的,它很可能不是,但如果有可能,您将不得不以某种方式复制字符串文字,或将它们用作 char[] 的初始值设定项。这可能涉及一个显式循环。main_under_testcharmain

评论

0赞 bers 10/26/2023
“请注意,如果实际尝试修改参数中的 s,这将不起作用。” - 是的,这将是未定义的行为。不过,很难防止这种情况发生。main_under_testchar
0赞 James Kanze 10/27/2023
@bers同意了。但是,如果是你写的代码,你就知道是否是这样。(此外,在实践中,只有最低限度的方法可以修改字符串;由于它们是 C 样式的字符串,因此不能使它们变大。main_under_test
0赞 Aconcagua 10/27/2023
可能存在潜在的风险,即虽然现在不修改,但同事(不知道这种方法)可能会更改代码,以便以后确实这样做 - 以这种方式破坏测试......