在函数范围之外运行C ++代码

时间:2014-09-16 08:39:33

标签: c++ static initialization initializer

(我知道)在c ++中我可以 declare variable超出范围,我无法运行任何代码/ statement,初始化全局/静态变量除外。


IDEA

使用棘手代码以下(例如)进行一些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 :)
}

10 个答案:

答案 0 :(得分:14)

解决它的一种方法是使用一个带有构造函数的类来执行操作,然后声明该类的虚拟变量。像

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

Initializer initializer;

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

答案 1 :(得分:11)

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

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

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

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

答案 2 :(得分:6)

  

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

没有。

任何带有可变非局部变量的解决方案都是一个糟糕的想法。

答案 3 :(得分:5)

  

这是一个好主意吗??

不是真的。如果有人在他们的&#34;棘手的初始化中做出决定&#34;他们想要使用你的地图,但是在某个系统或其他系统上,或者在特定的重新链接之后,由于不明显的原因,你的地图在尝试使用后最终会被初始化?如果你让他们调用一个静态函数来返回对地图的引用,那么它可以在第一次调用时初始化它。使映射成为该函数内的静态局部变量,并在没有这种保护的情况下停止任何意外使用。

答案 4 :(得分:4)

§8.5.2陈述

  

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

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

答案 5 :(得分:2)

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

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

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

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

你在问题​​中说过你自己认为代码很棘手&#39;没有必要为了它而使事情过于复杂。所以,如果你有一个看起来不那么棘手的替代方案。对你...可能会更好。

答案 6 :(得分:2)

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

让我们考虑更少的&#34;棘手的&#34;备选方案:

  1. 如果您只有一个 myMap 的全局实例非常重要,那么使用 Singleton Pattern 会更合适,你可以在需要时懒洋洋地初始化 myMap 的内容。请记住,Singleton模式有自己的问题。

  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;
    }
    

答案 7 :(得分:1)

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

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

答案 8 :(得分:1)

这是一个统一构建(单翻译单元构建)可能非常强大的情况。 __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

答案 9 :(得分: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;
});