【问题标题】:Running C++ code outside of functions scope在函数范围之外运行 C++ 代码
【发布时间】:2014-11-09 22:24:18
【问题描述】:

(我知道) 在 c++ 中,我 可以 declare variable 超出范围并且我不能运行任何代码/语句,除了用于初始化全局/静态变量。


想法

使用下面的棘手的代码来(例如)做一些std::map操作是个好主意吗?

这里我使用void *fakeVar 并通过Fake::initializer() 对其进行初始化,然后在其中做任何我想做的事情!

std::map<std::string, int> myMap;

class Fake
{
public:
    static void* initializer()
    {
        myMap["test"]=222;
        // Do whatever with your global Variables

        return NULL;
    }
};

// myMap["Error"] = 111;                  => Error
// Fake::initializer();                   => Error
void *fakeVar = Fake::initializer();    //=> OK

void main()
{
    std::cout<<"Map size: " << myMap.size() << std::endl; // Show myMap has initialized correctly :)
}

【问题讨论】:

  • 你为什么要为那个函数创建类?
  • @Dawid 那是我的错!
  • “这是个好主意吗”听起来相当基于意见。此外,“什么更好”(查看答案,实际上就是这样)是一个列表问题。
  • 每当您考虑“棘手的代码”时,您没有回答自己的问题吗?

标签: c++ static initialization initializer


【解决方案1】:

这个答案类似于Some programmer dude's answer,但可能被认为更简洁一些。从 C++17 开始(当时添加了 std::invoke()),您可以执行以下操作:

#include <functional>

auto initializer = std::invoke([]() {
    // Do initialization here...

    // The following return statement is arbitrary. Without something like it,
    // the auto will resolve to void, which will not compile:
    return true;
});

【讨论】:

  • 我喜欢你的回答,下次遇到同样问题时我会使用它。我没有将其标记为已接受答案的唯一原因是它仅限于 C++17,而当前接受的答案更通用。
【解决方案2】:

在这种情况下,统一构建(单个翻译单元构建)可能非常强大。 __COUNTER__ 宏是 C 和 C++ 编译器中的事实标准,使用它您可以在全局范围内编写任意命令式代码:

// At the beginning of the file...
template <uint64_t N> void global_function() { global_function<N - 1>(); } // This default-case skips "gaps" in the specializations, in case __COUNTER__ is used for some other purpose.
template <> void global_function<__COUNTER__>() {} // This is the base case.

void run_global_functions();

#define global_n(N, ...) \
template <> void global_function<N>() { \
    global_function<N - 1>(); /* Recurse and call the previous specialization */ \
    __VA_ARGS__; /* Run the user code. */ \
}
#define global(...) global_n(__COUNTER__, __VA_ARGS__)

// ...

std::map<std::string, int> myMap;

global({
    myMap["test"]=222;
    // Do whatever with your global variables
})
global(myMap["Error"] = 111);

int main() {
    run_global_functions();
    std::cout << "Map size: " << myMap.size() << std::endl; // Show myMap has initialized correctly :)
}

global(std::cout << "This will be the last global code run before main!");


// ...At the end of the file

void run_global_functions() {
    global_function<__COUNTER__ - 1>();
}

一旦您意识到可以使用它来初始化静态变量而不依赖于 C 运行时,这将特别强大。这意味着您可以生成非常小的可执行文件,而无需避开非零全局变量:

// At the beginning of the file...
extern bool has_static_init;
#define default_construct(x) x{}; global(if (!has_static_init()) new (&x) decltype(x){})
// Or if you don't want placement new:
// #define default_construct(x) x{}; global(if (!has_static_init()) x = decltype(x){})

class Complicated {
    int x = 42;
    Complicated() { std::cout << "Constructor!"; }
}
Complicated default_construct(my_complicated_instance); // Will be zero-initialized if the CRT is not linked into the program.

int main() {
    run_global_functions();
}

// ...At the end of the file
static bool get_static_init() {
    volatile bool result = true; // This function can't be inlined, so the CRT *must* run it.
    return result;
}
has_static_init = get_static_init(); // Will stay zero without CRT

【讨论】:

    【解决方案3】:

    当我听到“棘手的代码”时,我会立即想到代码异味和维护噩梦。要回答你的问题,不,这不是一个好主意。虽然它是有效的 C++ 代码,但这是不好的做法。对于这个问题,还有其他更明确和更有意义的替代方案。详细地说,您的 initializer() 方法返回 void* NULL 的事实就您的程序的意图而言毫无意义(即您的代码的每一行都应该具有有意义的目的),而您现在有了还有一个不必要的全局变量fakeVar,它不必要地指向NULL。

    让我们考虑一些不那么“棘手”的替代方案:

    1. 如果您只有一个 myMap 的全局实例非常重要,那么使用 单例模式 可能更合适,并且您可以在需要时延迟初始化 myMap 的内容。请记住,单例模式有其自身的问题。

    2. 使用静态方法创建并返回地图或使用全局命名空间。例如,类似这样的内容:

      // global.h
      namespace Global
      {
          extern std::map<std::string, int> myMap;
      };
      
      // global.cpp
      namespace Global
      {
          std::map<std::string, int> initMap()
          {
              std::map<std::string, int> map;
              map["test"] = 222;
              return map;
          }
      
          std::map<std::string, int> myMap = initMap();
      };
      
      // main.cpp
      #include "global.h"
      
      int main()
      {
         std::cout << Global::myMap.size() << std::endl;
         return 0;
      }
      
    3. 如果这是具有特殊功能的地图,创建自己的类(最佳选择)!虽然这不是一个完整的示例,但您明白了:

      class MyMap
      {
      private:
          std::map<std::string, int> map;
      
      public:
      
          MyMap()
          {
              map["test"] = 222;
          }
      
          void put(std::string key, int value)
          {
              map[key] = value;
          }
      
          unsigned int size() const
          {
              return map.size();
          }
      
          // Overload operator[] and create any other methods you need
          // ...
      };
      
      MyMap myMap;
      
      int main()
      {
         std::cout << myMap.size() << std::endl;
         return 0;
      }
      

    【讨论】:

    • 你的最后一个选项(把所有东西都放在课堂上)不是这个问题,并且不能满足在函数范围之外操作 myMap 的目标。
    【解决方案4】:

    您所做的是完全合法的 C++。所以,如果它对你有用,并且任何使用该代码的人都可以维护和理解,那就没问题了。不过,Joachim Pileborg 的样本对我来说更清楚。

    如果在初始化期间它们相互使用,可能会出现这样的初始化全局变量的问题。在这种情况下,确保变量以正确的顺序初始化可能会很棘手。出于这个原因,我更喜欢创建 InitializeX、InitializeY 等函数,并从 Main 函数中以正确的顺序显式调用它们。

    错误的顺序也可能导致程序退出期间出现问题,当全局变量中的一些可能已被破坏时,它们仍会尝试相互使用。同样,在 Main 返回之前以正确顺序进行一些显式销毁调用可以使其更清晰。

    所以,如果它适合你,那就去吧,但要注意其中的陷阱。同样的建议适用于 C++ 中的几乎所有功能!

    您在问题中说您自己认为代码“棘手”。没有必要为了它而把事情复杂化。所以,如果你有一个看起来不那么“棘手”的替代方案……那可能会更好。

    【讨论】:

    【解决方案5】:

    使用以下棘手的代码是否是个好主意(例如) 做一些 std::map 操作?

    没有。

    任何需要可变非局部变量的解决方案都是一个糟糕的主意。

    【讨论】:

      【解决方案6】:

      您不需要假类...您可以使用 lambda 进行初始化

      auto myMap = []{
          std::map<int, string> m;
          m["test"] = 222;
          return m;
      }();
      

      或者,如果只是普通数据,则初始化地图:

      std::map<std::string, int> myMap { { "test", 222 } };
      

      【讨论】:

      • 感谢 lambda,但就我而言,myMap 应该由不同的类初始化。
      • 我喜欢你甚至可以使用 lambdas 进行 const 声明。
      【解决方案7】:

      这是个好主意吗...?

      不是真的。如果有人决定在他们的“棘手初始化”中使用您的地图,但在某个系统或其他系统上,或者在特定重新链接后出于不明显的原因,您的地图最终在他们尝试使用后被初始化怎么办?如果您改为让他们调用一个返回对地图的引用的静态函数,那么它可以在第一次调用时对其进行初始化。使地图成为该函数内的静态局部变量,并且在没有此保护的情况下停止任何意外使用。

      【讨论】:

      • 谢谢。提到了here
      【解决方案8】:

      在 C++ 中,不能有任何函数之外的语句。但是,您声明了全局对象,并且在 main 启动之前对这些全局对象的构造函数(初始化程序)调用是自动的。在您的示例中,fakeVar 是一个全局指针,它通过类静态范围的函数进行初始化,这绝对没问题。
      即使是全局对象也会提供全局对象构造函数进行所需的初始化。 例如,

      class Fake
      {
      public:
          Fake()     {
              myMap["test"]=222;
              // Do whatever with your global Variables
          }
      };
      Fake fake; 
      

      【讨论】:

        【解决方案9】:

        § 8.5.2 状态

        使用 constexpr 说明符声明的对象除外,对于 见 7.1.5,变量定义中的初始化器可以包括 涉及文字和先前声明的任意表达式 变量和函数,与变量的存储时间无关

        因此,C++ 标准完全允许您所做的事情。也就是说,如果您需要执行“初始化操作”,最好只使用类构造函数(例如包装器)。

        【讨论】:

          【解决方案10】:

          解决这个问题的一种方法是让一个类有一个构造函数,然后声明一个该类的虚拟变量。喜欢

          struct Initializer
          {
              Initializer()
              {
                  // Do pre-main initialization here
              }
          };
          
          Initializer initializer;
          

          您当然可以让多个这样的类进行杂项初始化。每个翻译单元中的顺序指定为自上而下,但翻译单元之间的顺序未指定。

          【讨论】:

          • 更一般地,我们可以给这个构造函数添加一些参数。
          • @Emadpres 没关系,你可以照常传递参数。
          • @Emadpres:这通常没用。从 Joachim 的示例中可以看出,传递的参数仅向上传递 5 行。为什么不把它们直接放在你需要的地方,在 Initializer::Initializer 里面?这不像Initializer initializer; 可以访问argc/argv 或其他有用的变量(在这个问题的上下文中)
          • @Emadpres:好吧,这遇到了地图不能是全局的问题,因为当其他文件中的初始化程序运行时它可能不存在。
          • 在这种情况下,您可以使用函数将地图声明为静态: map& GetMap() { static map myMap;返回我的地图; }。调用 GetMap 的第一个初始化程序将使其存在。
          猜你喜欢
          • 1970-01-01
          • 2013-12-08
          • 2015-12-10
          • 2015-10-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多