【问题标题】:Checking if a variable is initialized检查变量是否已初始化
【发布时间】:2011-10-12 22:00:32
【问题描述】:

这似乎是重复的,但也许它太明显了,还没有被问到......

这是检查变量(不是指针)是否在 C++ 类中初始化的正确方法吗?

class MyClass
{
    void SomeMethod();

    char mCharacter;
    double mDecimal;
};

void MyClass::SomeMethod()
{
    if ( mCharacter )
    {
        // do something with mCharacter.
    }

    if ( ! mDecimal )
    {
        // define mDecimal.
    }
}

【问题讨论】:

  • “已定义”是什么意思?你的意思是“具有价值”作为“初始化”?
  • 所有变量都有一个值(它们是被定义的)。通常编译器会将变量初始化为某个标准值,但我不确定这是否是 C++ 定义的一部分,据我所知不是。在使用它们之前初始化所有变量是一个很好的做法,这样它们就不会有一些随机或依赖于实现的值。然后您可以在使用它们之前随时检查它们的当前值。
  • @Jay,您确实需要改写您的问题以匹配 C++ 使用的单词。根据 C++ 规则,在您的示例中,mCharacter 总是在 MyClass 中定义。 'char mCharacter' 出现在那里意味着它已定义。现在您需要通过“是否定义了 mCharacter”来考虑您真正需要的内容。你的意思是,“分配给?”。 C++ 无法真正检查这一点。 “值与初始化值不同”?也许,但是,您的示例没有任何初始化值,因此您也无法检查它,除非您添加初始化程序和构造函数。在这种形式中,问题必须改写
  • 为什么要区分指针?指针变量只是具有指针类型的变量。
  • 随着 C++17 的出现,改变正确答案不是更好吗?

标签: c++ class


【解决方案1】:

如果你不喜欢 boost 和 c++17,google c++ lib Abseil 是实现这一点的另一种方法。 absl::optional 就像 std::optional 一样。

#include <absl/types/optional.h>
class MyClass
{
    void SomeMethod();

    absl::optional<char> mCharacter;
    absl::optional<double> mDecimal;
};

void MyClass::SomeMethod()
{
    if (mCharacter)
    {
        // do something with mCharacter.
    }

    if (!mDecimal)
    {
        // define mDecimal.
    }
}

【讨论】:

    【解决方案2】:

    对于 C++-11 或 Boost 库,您可以考虑使用智能指针存储变量。考虑这个 MVE,其中 toString() 的行为取决于 bar 是否被初始化:

    #include <memory>
    #include <sstream>
    
    class Foo {
    
    private:
        std::shared_ptr<int> bar;
    
    public:
        Foo() {}
        void setBar(int bar) {
            this->bar = std::make_shared<int>(bar);
        }
        std::string toString() const {
            std::ostringstream ss;
            if (bar)           // bar was set
                ss << *bar;
            else               // bar was never set
                ss << "unset";
            return ss.str();
        }
    };
    

    使用此代码

    Foo f;
    std::cout << f.toString() << std::endl;
    f.setBar(42);
    std::cout << f.toString() << std::endl;
    

    产生输出

    unset
    42
    

    【讨论】:

    • 这是否适用于非指针变量,例如Foo f; if (f) ... -- 它似乎对我不起作用,但也许还有其他方法?
    • 不,智能指针解决方案仅适用于指针变量。如果您需要将变量放入堆栈,我建议使用 Elmar 的解决方案,利用 std::optional
    • 什么是 MVE?最小可行示例?最小可行实验?
    • 我认为这是不正确的,因为智能指针的大小比您的变量大。它类似于为您的变量定义标志并在 setter 函数中设置为 true。
    • @Amir:你说得对,智能指针的使用意味着更大的内存占用。但是,该问题并不表明内存使用很重要。高级编程技术的许多努力旨在使事情发生自动,因此没有机会忘记做某事。使用智能指针或 Elmar 建议的 std::optional 断言您可以始终检查变量是否指示。根本没有机会忘记使用您指示的标志。另外:无需强制使用 setter 而不是直接访问变量。
    【解决方案3】:

    根据您的应用程序(尤其是如果您已经在使用 boost),您可能需要查看 boost::optional

    (更新:从 C++17 开始,可选现在是标准库的一部分,如 std::optional

    它具有您正在寻找的属性,跟踪插槽是否实际保存值。默认情况下,它被构造为不保存值并评估为 false,但如果它评估为 true,则允许取消引用它并获取包装的值。

    class MyClass
    {
        void SomeMethod();
    
        optional<char> mCharacter;
        optional<double> mDecimal;
    };
    
    void MyClass::SomeMethod()
    {
        if ( mCharacter )
        {
            // do something with *mCharacter.
            // (note you must use the dereference operator)
        }
    
        if ( ! mDecimal )
        {
            // call mDecimal.reset(expression)
            // (this is how you assign an optional)
        }
    }
    

    更多示例在the Boost documentation

    【讨论】:

      【解决方案4】:

      使用 C++17,您可以使用 std::optional 检查变量是否已初始化:

      #include <optional>
      #include <iostream>  // needed only for std::cout
      
      int main() {
          std::optional<int> variable;
      
          if (!variable) {
              std::cout << "variable is NOT initialized\n";
          }
      
          variable = 3;
      
          if (variable) {
              std::cout << "variable IS initialized and is set to " << *variable << '\n';
          }
      
          return 0;
      }
      

      这将产生输出:

      variable is NOT initialized
      variable IS initialized and is set to 3
      

      要在您提供的代码片段中使用std::optional,您必须包含&lt;optional&gt; 标准库头并将std::optional&lt;...&gt; 添加到相应的变量声明中:

      #include <optional>
      
      class MyClass
      {
          void SomeMethod();
      
          std::optional<char> mCharacter;
          std::optional<double> mDecimal;
      };
      
      void MyClass::SomeMethod()
      {
          if ( mCharacter )
          {
              std::cout << *mCharacter;  // do something with mCharacter.
          }
      
          if ( ! mDecimal )
          {
              mDecimal = 3.14159;  // define mDecimal.
          }
      }
      

      【讨论】:

        【解决方案5】:

        您可以在断言中引用该变量,然后使用-fsanitize=address 构建:

        void foo (int32_t& i) {
            // Assertion will trigger address sanitizer if not initialized:
            assert(static_cast<int64_t>(i) != INT64_MAX);
        }
        

        这将导致程序可靠地崩溃并显示堆栈跟踪(与未定义的行为相反)。

        【讨论】:

          【解决方案6】:

          例如,如果您使用字符串而不是字符,则可以执行以下操作:

              //a is a string of length 1
              string a;
              //b is the char in which we'll put the char stored in a
              char b;
              bool isInitialized(){
                if(a.length() != NULL){
                  b = a[0];
                  return true;
                }else return false;
              }
          

          【讨论】:

            【解决方案7】:

            没有合理的方法来检查一个值是否已经初始化。

            如果您关心某些内容是否已初始化,而不是尝试检查它,请将代码放入构造函数以确保它们始终被初始化并完成。

            【讨论】:

              【解决方案8】:

              由于 MyClass 是 POD 类类型,当您创建 MyClass 的非静态实例时,这些非静态数据成员将具有不确定的初始值,所以不,这不是检查它们是否有效的方法已被初始化为特定的非零值...您基本上假设它们将被零初始化,但事实并非如此,因为您没有在构造函数中对它们进行值初始化。

              如果您想对类的非静态数据成员进行零初始化,最好创建一个初始化列表和类构造函数。例如:

              class MyClass
              {
                  void SomeMethod();
              
                  char mCharacter;
                  double mDecimal;
              
                  public:
                      MyClass();
              };
              
              MyClass::MyClass(): mCharacter(0), mDecimal(0) {}
              

              上面构造函数中的初始化列表将您的数据成员值初始化为零。您现在可以正确地假设 mCharactermDecimal 的任何非零值必须是您在代码中的其他位置专门设置的,并且包含您可以正确操作的非零值。

              【讨论】:

              • 在实例化对象时,成员变量不会被初始化为默认值或随机值除非它是在 c-tor 中明确完成的。许多调试运行时将在分配内存时将内存初始化为特定值(新),否则它是 UB,该值将是实例化之前包含的任何内存。
              • MyClass 的默认构造函数,因为它不会对 OP 原始版本中的任何变量进行值初始化,将为数据成员创建随机值......随机值(即,值已存在于内存中)是 POD 数据类型的默认初始化。
              • "default-initialized with random values" - 该语句暗示默认情况下会发生某种类型的初始化,它将随机值存储在变量中。正确的术语只是“未初始化”
              • 我真的不想听起来像这样的纳粹 ;)
              【解决方案9】:

              C++ 语言无法检查变量是否已初始化(尽管具有构造函数的类类型会自动初始化)。

              相反,您需要做的是提供将您的类初始化为有效状态的构造函数。静态代码检查器(可能还有一些编译器)可以帮助您在构造函数中找到缺失的变量。这样您就不必不必担心处于虚假状态,并且您的方法中的 if 检查可以完全消失。

              【讨论】:

                【解决方案10】:

                默认情况下,您无法知道变量(或指针)是否已初始化。然而,既然其他人都在告诉你“简单”或“正常”的方法,我会给你一些别的想法。以下是您可以跟踪此类事情的方法(不,我个人永远不会这样做,但也许您的需求与我不同)。

                class MyVeryCoolInteger
                {
                public:
                    MyVeryCoolInteger() : m_initialized(false) {}
                
                    MyVeryCoolInteger& operator=(const int integer)
                    {
                        m_initialized = true;
                        m_int = integer;
                        return *this;
                    }
                
                    int value()
                    {
                        return m_int;
                    }
                
                    bool isInitialized()
                    {
                        return m_initialized;
                    }
                
                private:
                    int m_int;
                    bool m_initialized;
                };
                

                【讨论】:

                • 你很酷的整数存在,更抽象地作为一个模板,被称为boost::optional。 (请参阅我的回答。)如果您遇到这种情况,使用它并没有错。当然,如果您有数百万个整数,那么选择一个神奇的值来表示“没有价值”可能会很划算。
                • 哦,毫无疑问。但如前所述,我想我会给出一个替代答案。
                【解决方案11】:

                如果你的意思是如何检查成员变量是否已经被初始化,你可以通过在构造函数中为它们分配哨兵值来做到这一点。选择标记值作为在该变量的正常使用中永远不会出现的值。如果一个变量的整个范围都被认为是有效的,你可以创建一个布尔值来指示它是否已经被初始化。

                #include <limits>
                
                class MyClass
                {
                    void SomeMethod();
                
                    char mCharacter;
                    bool isCharacterInitialized;
                    double mDecimal;
                
                    MyClass()
                    : isCharacterInitialized(false)
                    , mDecimal( std::numeric_limits<double>::quiet_NaN() )
                    {}
                
                
                };
                
                
                void MyClass::SomeMethod()
                {
                    if ( isCharacterInitialized == false )
                    {
                        // do something with mCharacter.
                    }
                
                    if ( mDecimal != mDecimal ) // if true, mDecimal == NaN
                    {
                        // define mDecimal.
                    }
                }
                

                【讨论】:

                  【解决方案12】:

                  没有办法检查变量的内容是否未定义。您可以做的最好的事情是分配一个信号/哨兵值(例如在构造函数中)以指示需要执行进一步的初始化。

                  【讨论】:

                  • 不幸的是,这个答案是不正确的。如果您考虑使用 C++-11,有一个类成员的实现而不使用哨兵值,请参阅下面的答案。
                  • @Twonky 还是正确的。使用智能指针或std::optional 类似于使用bool(“信号”)来标记变量是否已初始化。
                  【解决方案13】:

                  未定义的变量会导致编译错误。

                  您要问的是检查它是否已初始化。但是初始化只是一个值,你应该在构造函数中选择和赋值。

                  例如:

                  class MyClass
                  {
                      MyClass() : mCharacter('0'), mDecimal(-1.0){};
                      void SomeMethod();
                  
                      char mCharacter;
                      double mDecimal;
                  };
                  
                  void MyClass::SomeMethod()
                  {
                      if ( mCharacter != '0')
                      {
                          // touched after the constructor
                          // do something with mCharacter.
                      }
                  
                      if ( mDecimal != -1.0 )
                      {
                          // touched after the constructor
                          // define mDecimal.
                      }
                  }
                  

                  当然,您应该初始化为一个默认值,该值在您的逻辑上下文中具有一定的意义。

                  【讨论】:

                  • 添加到这个答案中,您可以在 c-tor 中设置一个无效值,并在您的方法中检查它是否仍然无效。或使用布尔值来标记值已从初始无效值更改
                  • @NirMH 我几乎总是会说,如果您没有在构造时创建有效对象的信息,则应该删除该构造函数,以支持具有足够信息的构造函数来创建有效对象。
                  • 这正是@alexander-gessler 对他的信号/哨兵价值的意思
                  猜你喜欢
                  • 2016-10-03
                  • 2015-10-24
                  • 1970-01-01
                  • 2022-12-03
                  • 2022-01-08
                  • 2014-08-19
                  相关资源
                  最近更新 更多