当"taking address of temporary"错误妨碍干净的代码时

When "taking address of temporary" error gets in a way of clean code

本文关键字:代码 错误 address taking of temporary      更新时间:2023-10-16

我知道这个错误消息被多次讨论过,其背后的原因是众所周知的。这个问题是关于不同的事情 - 如何编码某种模式(如果不是因为这个错误,这将是微不足道的)。

有一个函数void foo(int const*, int const*, int const*),当相关数据不可用时,每个参数都会nullptr。我使用了int,但它可以是任何类型的(参数的数量也不是固定的)。

使用MSVC(或在过去 - 使用-fpermissive),您可以编写这样的代码,并且很容易看:

int data_source::bar() { ... }
data_source* s1;
data_source* s2;
data_source* s3;
...
foo(
s1 ? &s1->bar() : nullptr,
s2 ? &s2->bar() : nullptr,
s3 ? &s3->bar() : nullptr
);

现在,对于现代 GCC,这无法编译,-fpermissive似乎不再有效。

您将如何构建此代码以在避免错误的同时执行相同的操作?有没有办法告诉编译器"是的,我知道,但没关系"(类似于(void)x强制转换以避免unused variable警告)?

我想出了两个"解决方案"(不喜欢它们):

template<class T> T const* addr(T const& v) { return &v; }

甚至更危险:

template<class T> T* addr(T&& v) { return &v; }

这意味着更多的打字,更难阅读,而且通常很丑陋。

这个试图模仿编译器在幕后所做的事情,但不幸的是,导致太多的类型,一个额外的比较(每个参数)以及生成的asm中的其他低效率:

optional<int> v1 = [&]{ return s1 ? optional<int>(s1->bar()) : optional<int>(); }();
optional<int> v2 = [&]{ return s2 ? optional<int>(s2->bar()) : optional<int>(); }();
optional<int> v3 = [&]{ return s3 ? optional<int>(s3->bar()) : optional<int>(); }();
foo(
v1 ? &v1.value() : nullptr,
v2 ? &v2.value() : nullptr,
v3 ? &v3.value() : nullptr
);

问题不在于语言与干净的代码冲突。问题在于,您使用 C 形式的"可选"参数作为误导的微优化形式。C++17 为此引入了一种词汇类型,恰如其分地命名为std::optional。我认为foo最好看起来像这样:

void foo(std::optional<int>, std::optional<int>, std::optional<int>);

它具有干净的编码人员应该喜欢的自我记录功能。由于它也按值传递,因此无需在接口中人为地将其标记为const。最后,调用不是问题:

auto extract_argument = [](data_source* s) {
return s ? optional<int>{s->bar()} : optional<int>{};
};
foo(
extract_argument(s1),
extract_argument(s2),
extract_argument(s2)
);

您标记了C++11,所以我不会触及上面可能会boost::optional作为替身掉落的地方。但应该注意的是,在 C++17 中,它几乎可以与原始代码相同:

foo(
s1 ? std::optional{s1->bar()} : std::nullopt,
s2 ? std::optional{s2->bar()} : std::nullopt,
s3 ? std::optional{s3->bar()} : std::nullopt
);

指定std::optional是必需的,因为我们希望条件表达式具有明确定义的类型,但可以使用std::nullopt指定"null"大小写。更重要的是,类模板参数推导意味着我们不需要指定模板参数。所以它简短,富有表现力,切中要害。

解决方案#1 C++代码是正确的,显然是实现我的目标的唯一方法:

template<class T> T* addr(T&& v) { return &v; }
foo(
s1 ? addr(s1->bar()) : nullptr,
s2 ? addr(s2->bar()) : nullptr,
s3 ? addr(s3->bar()) : nullptr
);

进一步说明:由于C++14获取右值的地址不再是UB-现在只是禁止,编译器需要发出错误。

如果foo尝试取消引用它作为参数获得的非 NULL 指针,则调用它

foo(
s1 ? &s1->bar() : nullptr,
s2 ? &s2->bar() : nullptr,
s3 ? &s3->bar() : nullptr
);

导致未定义的行为。如果你宁愿找到一种方法告诉编译器"我知道我在做什么。闭嘴,去做吧",你粗心大意,傲慢自大。

如果foo没有取消引用非 NULL 指针,则可以使用:

int temp;
foo(
s1 ? &temp : nullptr,
s2 ? &temp : nullptr,
s3 ? &temp : nullptr
);

并避免调用data_source::bar().

无论哪种情况,都使用

foo(
s1 ? &s1->bar() : nullptr,
s2 ? &s2->bar() : nullptr,
s3 ? &s3->bar() : nullptr
);

完全没有保证。