根据条件声明一个大变量的方法

5
我是一名有用的助手,可以为您翻译文本。
正在阅读C++核心指南,并遇到了这条规则:“在初始化变量之前不要声明变量”。https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es22-dont-declare-a-variable-until-you-have-a-value-to-initialize-it-with 它将以下代码描述为不好的:
SomeLargeType var;

if (cond)   // some non-trivial condition
    Set(&var);
else if (cond2 || !cond3) {
    var = Set2(3.14);
}
else {
    var = 0;
    for (auto& e : something)
        var += e;
}

很遗憾,这个点没有描述如何解决这个确切的问题的方法。有时候你只需要根据条件以不同的方式初始化一个大对象。 我能想到的唯一的绕过方法是像这样:

SomeLargeType * var;

if (cond)   // some non-trivial condition
    var = new SomeLargeType(123);
else if (cond2 || !cond3) {
    var = new SomeLargeType(3.14);
}

然而,即使我使用了智能指针,这种方式仍然感觉不必要/不安全,最重要的是比最初的方法更糟糕。最佳解决方案是什么?

也许我只是困惑或者愚蠢?但是,就我看来,这个问题中的建议和给出的答案都没有正确地处理"糟糕的代码示例"中的最后一个else情况。 - Adrian Mole
1
你的文章中有一个链接,回答了你的问题。 - user1143634
有人刚才恢复了@StaceyGirl的评论吗?如果是这样,谢谢 - 我会将其粘贴到我的答案中,以实现完全自我包含。 - Adrian Mole
3个回答

5

您可以使用函数。此外,不要使用具有所有权的裸指针(我假设这也有一个指南)。例如:

std::unique_ptr<SomeLargeType>
make_something(bool cond, bool cond23)
{
    if (cond)
        return std::make_unique<SomeLargeType>(123);
    else if (cond23)
        return std::make_unique<SomeLargeType>(3.14);
    else
        return nullptr;
}

// usage
std::unique_ptr<SomeLargeType> var = make_something(cond, cond2 || !cond3);

如果这个函数没有可重用性,那么使用lambda表达式可能是合适的,就像Sopel所示的那样。

2

首先,原始示例代码中不存在未定义行为:所有可能的条件都已得到处理(通过最终的 else 块)。然而,在迄今为止所给出的答案中存在潜在的未定义行为,因为它们用实际上没有初始化并返回 nullptr 的方式替换了前述 else 块中的“良好”代码,这可能会成为稍后尝试解引用的主题。

此外,在这里没有真正必要通过将实例变量替换为指针来复杂化问题(这也会改变代码的性质/逻辑)。

使用lambda表达式(如由StaceyGirl提供的链接所建议的)肯定是一种不错的方式(可能是最好的,但这可能是主观的)。 但是,为了保持与原始代码相同的逻辑,可以将lambda应用于对象,而不是指针,如下所示:

SomeLargeType var = [&]() {
    if (cond) {   // some non-trivial condition
        SomeLargeType v1;
        Set(&v1);
        return v1;
    }
    else if (cond2 || !cond3) {
        SomeLargeType v2 = Set2(3.14);
        return v2;
        }
    else {
        SomeLargeType v3 = 0;
        for (auto& e : something) var += e;
        return v3;
    }
}();

在这里,与原始代码不同(在原始代码中首先调用默认构造函数,然后调用三个其他构造函数之一),SomeLargeObject的构造函数只会被调用一次¹,并且不会出现未定义行为。 我认为,这个初始调用(可能非常昂贵)的默认构造函数,是这个例子被引用为"糟糕代码"的原因。

¹ 如果对构造函数的调用频率有任何疑问,我可以提供一个完整的MCVE(稍作修改以避免未定义的for(auto& e : something)行),如果需要的话。


2

立即调用的lambda表达式可作为命名函数的替代方案。

SomeLargeType* var = [&]() {
    if (cond)   // some non-trivial condition
        return new SomeLargeType(123);
    else if (cond2 || !cond3)
        return new SomeLargeType(3.14);
    else
        return nullptr;
}();

请注意,某些类型可能没有提供默认构造函数,这种情况下,如果不使用装箱类型,则无法进行延迟初始化。当类型不是可平凡构造时,这也可以提高性能。 https://godbolt.org/z/_V8t2T

@StaceyGirl,那是原帖作者的问题。 - Sopel
@Sopel那么,如果我们只是从一个未定义行为跳到另一个未定义行为,那么所有这些lambda的无聊有什么意义呢? - user1143634
@Sopel 仅为了遵循编码准则而遵循编码准则是一种不好的实践。 - user1143634
@StaceyGirl 你在说什么呢?在添加 return nullptr 之前,当两个条件都为 false 时,代码会产生未定义行为。我添加它是为了使行为等同于 OPs。 - Sopel
@StaceyGirl 我们不需要很多东西。 - Sopel
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接