让我们从一些代码开始。这是我程序的极为简化的版本。
#include <stdint.h>
volatile uint16_t dummyColorRecepient;
void updateColor(const uint8_t iteration)
{
uint16_t colorData;
switch(iteration)
{
case 0:
colorData = 123;
break;
case 1:
colorData = 234;
break;
case 2:
colorData = 345;
break;
}
dummyColorRecepient = colorData;
}
// dummy main function
int main()
{
uint8_t iteration = 0;
while (true)
{
updateColor(iteration);
if (++iteration == 3)
iteration = 0;
}
}
程序编译时显示警告:
./test.cpp: In function ‘void updateColor(uint8_t)’:
./test.cpp:20:25: warning: ‘colorData’ may be used uninitialized in this function [-Wmaybe-uninitialized]
dummyColorRecepient = colorData;
~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~
如您所见,绝对可以确定变量iteration
始终为0
,1
或2
。但是,编译器不知道这一点,并且假定开关可能未初始化colorData
。 (编译期间进行的大量静态分析都无济于事,因为实际程序分散在多个文件中。)
当然,我可以只添加一个默认语句,例如default: colorData = 0;
,但这会为程序增加24个字节。 这是一个用于微控制器的程序,我对其大小有严格的限制。
我想通知编译器,保证此开关可以覆盖iteration
的所有可能值。
答案 0 :(得分:10)
如您所见,可以绝对确定变量迭代始终为0、1或2。
从工具链的角度来看,这是不正确的。您可以从其他地方调用此函数,甚至可以从另一个翻译单元调用。强制执行约束的唯一位置是在main
中,即使执行约束的方式也使得编译器难以推断可能。
不过,出于我们的目的,让我们看一下您不打算链接任何其他翻译单元,并且我们想告诉工具链有关此的信息。好吧,幸运的是,我们可以!
如果您不介意携带不便,那么GCC's __builtin_unreachable
built-in会通知您,default
案件预计不会发生,应视为不可到达。我的海湾合作委员会很聪明,知道这意味着colorData
永远都不会被未初始化,除非所有的赌注都被取消了。
#include <stdint.h>
volatile uint16_t dummyColorRecepient;
void updateColor(const uint8_t iteration)
{
uint16_t colorData;
switch(iteration)
{
case 0:
colorData = 123;
break;
case 1:
colorData = 234;
break;
case 2:
colorData = 345;
break;
// Comment out this default case to get the warnings back!
default:
__builtin_unreachable();
}
dummyColorRecepient = colorData;
}
// dummy main function
int main()
{
uint8_t iteration = 0;
while (true)
{
updateColor(iteration);
if (++iteration == 3)
iteration = 0;
}
}
这不会添加实际的default
分支,因为其中没有“代码”。实际上,当我使用带有-O2
的x86_64 GCC将其插入Godbolt时,该程序比没有该程序的小小-从逻辑上讲,您刚刚添加了一个 major < / em>优化提示。
实际上有a proposal to make this a standard attribute in C++,因此将来可能会成为更具吸引力的解决方案。
答案 1 :(得分:5)
使用“立即调用的lambda表达式”习惯用法和assert
:
void updateColor(const uint8_t iteration)
{
const auto colorData = [&]() -> uint16_t
{
switch(iteration)
{
case 0: return 123;
case 1: return 234;
}
assert(iteration == 2);
return 345;
}();
dummyColorRecepient = colorData;
}
lambda表达式使您可以将colorData
标记为const
。 const
变量必须始终被初始化。
assert
+ return
语句的组合使您可以避免警告并处理所有可能的情况。
assert
不在发布模式下进行编译,从而避免了开销。
您还可以分解该功能:
uint16_t getColorData(const uint8_t iteration)
{
switch(iteration)
{
case 0: return 123;
case 1: return 234;
}
assert(iteration == 2);
return 345;
}
void updateColor(const uint8_t iteration)
{
const uint16_t colorData = getColorData(iteration);
dummyColorRecepient = colorData;
}
答案 2 :(得分:3)
只需在其中一种情况下添加default
标签,就可以在没有警告的情况下进行编译:
switch(iteration)
{
case 0:
colorData = 123;
break;
case 1:
colorData = 234;
break;
case 2: default:
colorData = 345;
break;
}
或者:
uint16_t colorData = 345;
switch(iteration)
{
case 0:
colorData = 123;
break;
case 1:
colorData = 234;
break;
}
尝试两者,并使用两者中较短的一个。
答案 3 :(得分:1)
我知道有一些很好的解决方案,但是可以选择If
在编译时知道您的值,而不是switch语句,您可以将constexpr
与static function template
一起使用和几个enumerators
;在单个类中看起来像这样:
#include <iostream>
class ColorInfo {
public:
enum ColorRecipient {
CR_0 = 0,
CR_1,
CR_2
};
enum ColorType {
CT_0 = 123,
CT_1 = 234,
CT_2 = 345
};
template<const uint8_t Iter>
static constexpr uint16_t updateColor() {
if constexpr (Iter == CR_0) {
std::cout << "ColorData updated to: " << CT_0 << '\n';
return CT_0;
}
if constexpr (Iter == CR_1) {
std::cout << "ColorData updated to: " << CT_1 << '\n';
return CT_1;
}
if constexpr (Iter == CR_2) {
std::cout << "ColorData updated to: " << CT_2 << '\n';
return CT_2;
}
}
};
int main() {
const uint16_t colorRecipient0 = ColorInfo::updateColor<ColorInfo::CR_0>();
const uint16_t colorRecipient1 = ColorInfo::updateColor<ColorInfo::CR_1>();
const uint16_t colorRecipient2 = ColorInfo::updateColor<ColorInfo::CR_2>();
std::cout << "\n--------------------------------\n";
std::cout << "Recipient0: " << colorRecipient0 << '\n'
<< "Recipient1: " << colorRecipient1 << '\n'
<< "Recipient2: " << colorRecipient2 << '\n';
return 0;
}
cout
中的if constexpr
语句仅出于测试目的而添加,但这应说明另一种可能的方法,而不必使用switch语句,前提是您的值将在编译时知道。如果这些值是在运行时生成的,则不能完全确定是否有一种方法可以使用constexpr
来实现这种代码结构,但是如果有其他方面的经验,我将不胜感激可以详细说明如何使用constexpr
值对runtime
进行此操作。但是,由于没有magic numbers
,因此该代码可读性强。
-更新-
在阅读了有关constexpr
的更多信息后,我注意到它们可以用于生成compile time constants
。我还了解到它们无法生成runtime constants
,但可以在runtime function
中使用它们。通过将static function
添加到类中,我们可以采用上述类结构并将其在运行时函数中使用:
static uint16_t colorUpdater(const uint8_t input) {
// Don't forget to offset input due to std::cin with ASCII value.
if ( (input - '0') == CR_0)
return updateColor<CR_0>();
if ( (input - '0') == CR_1)
return updateColor<CR_1>();
if ( (input - '0') == CR_2)
return updateColor<CR_2>();
return updateColor<CR_2>(); // Return the default type
}
但是,我想更改两个函数的命名约定。我将第一个函数命名为colorUpdater()
,上面刚刚显示的这个新函数将命名为updateColor()
,因为这样看起来更直观。因此,更新的类现在将如下所示:
class ColorInfo {
public:
enum ColorRecipient {
CR_0 = 0,
CR_1,
CR_2
};
enum ColorType {
CT_0 = 123,
CT_1 = 234,
CT_2 = 345
};
static uint16_t updateColor(uint8_t input) {
if ( (input - '0') == CR_0 ) {
return colorUpdater<CR_0>();
}
if ( (input - '0') == CR_1 ) {
return colorUpdater<CR_1>();
}
if ( (input - '0') == CR_2 ) {
return colorUpdater<CR_2>();
}
return colorUpdater<CR_0>(); // Return the default type
}
template<const uint8_t Iter>
static constexpr uint16_t colorUpdater() {
if constexpr (Iter == CR_0) {
std::cout << "ColorData updated to: " << CT_0 << '\n';
return CT_0;
}
if constexpr (Iter == CR_1) {
std::cout << "ColorData updated to: " << CT_1 << '\n';
return CT_1;
}
if constexpr (Iter == CR_2) {
std::cout << "ColorData updated to: " << CT_2 << '\n';
return CT_2;
}
}
};
如果只想将其与编译时间常数一起使用,则可以像以前一样使用它,但是要使用函数的更新名称。
#include <iostream>
int main() {
auto output0 = ColorInfo::colorUpdater<ColorInfo::CR_0>();
auto output1 = ColorInfo::colorUpdater<ColorInfo::CR_1>();
auto output2 = ColorInfo::colorUpdater<ColorInfo::CR_2>();
std::cout << "\n--------------------------------\n";
std::cout << "Recipient0: " << output0 << '\n'
<< "Recipient1: " << output1 << '\n'
<< "Recipient2: " << output2 << '\n';
return 0;
}
如果您想将此机制与runtime
值一起使用,则可以执行以下操作:
int main() {
uint8_t input;
std::cout << "Please enter input value [0,2]\n";
std::cin >> input;
auto output = ColorInfo::updateColor(input);
std::cout << "Output: " << output << '\n';
return 0;
}
这将与运行时值一起工作。
答案 4 :(得分:0)
好吧,如果您确定不必处理其他可能的值,则可以使用算术。摆脱了分支和负担。
void updateColor(const uint8_t iteration)
{
dummyColorRecepient = 123 + 111 * iteration;
}