【问题标题】:Collecting information on which template variants are being instantiated in a program收集有关在程序中实例化哪些模板变体的信息
【发布时间】:2017-08-30 21:47:03
【问题描述】:

今天我了解到,当我们有一个带有静态成员变量的 C++ 类模板时,除非我们“以需要定义要存在的静态数据成员”。

这个现象在这里得到了很好的解释: C++ Static member initalization (template fun inside)

在实践中,这意味着如果我们想要初始化(以及它的任何可能的副作用)发生,我们必须显式地引用该静态成员的每个实例化(从类模板外部)。

我一直在想办法解决这个问题。

我的动机是有一个现有的代码库,它使用类模板Foo 的各种实例化(它有多个模板参数,但为了示例我简化了),我想自动收集有关所有不同的参数组合。

我实际上不能等待在程序执行期间构建所有这些Foos(这是一个长时间运行的后台进程),所以我认为我可以在Foo<T> 中放置一个静态Initializer<T> 并让它提取所需的在程序启动后立即为每个不同的Foo 类型键入信息。

在这种情况下,为了让初始化程序首先运行而必须枚举Initializer<T> Foot<T>::init 的所有实例显然违背了目的。我必须去看看(在整个项目中)这些类型是什么,而这正是我想要实现的自动化。

我注意到,如果我用持有本地静态Initializer 实例的静态方法替换静态成员变量,我可以更轻松地强制生成Initializer<T> 定义。我只需要获取一个指向该方法的指针(例如在Foo 的构造函数中)。

最后一步是在程序启动后调用这个静态方法。在 g++/clang 的情况下,使用 __attribute__((constructor)) 就像一个魅力。

不过,我还必须处理 MSVC++,这就是我想出的:

#include <iostream>

#if defined(_MSC_VER) && !defined(__clang__)
#define MSVC_CONSTRUCTOR_HACK
#define ATTRIBUTE_CONSTRUCTOR
#else
#define ATTRIBUTE_CONSTRUCTOR __attribute__((constructor))
#endif

static int& gInt() { // Global counter
    static int i;
    return i;
}

template <class T>
struct Initializer {
    // If it works, this gets called for each Foo<T> declaration
    Initializer() {
        gInt()++;
    }
};


#ifdef MSVC_CONSTRUCTOR_HACK
__pragma(section(".CRT$XCU", read))
template <class T>  // This will hold pointers to Foo<T>::getInit
static void(*g_constructors__)(void);
#endif

template <class T>
struct Foo {
    ATTRIBUTE_CONSTRUCTOR // Empty in case of MSVC
    static void getInit() {
        static Initializer<T> init;
    }

#ifdef MSVC_CONSTRUCTOR_HACK
    template <> // Why is this allowed?!
    __declspec(allocate(".CRT$XCU")) static void(*g_constructors__<T>)(void) = getInit;
#endif

    Foo() { // This never gets called and we want that
        std::cout << "Constructed Foo!" << std::endl; 
        (void)&getInit; // This triggers instantiation and definition of Initializer<T>
    }
};


void unused() {
    Foo<char> c;
    Foo<double> d;
    Foo<int> i;
    Foo<float> f;
}

int main() {
    std::cout << gInt() << std::endl; // prints 4
    return 0;
}

它依赖于将函数指针放入可执行文件的 .CRT 部分(https://stackoverflow.com/a/2390626/6846474https://github.com/djdeath/glib/blob/master/glib/gconstructor.h)。

不过,为了让它在这种情况下工作,我还不得不求助于这个非常奇怪的技巧:有一个全局变量模板g_constructors__,它在内部明确专门化(!)Foo

说实话,我真的很惊讶这个作品。我知道它是非标准的,但有人可以解释它是如何编译的吗?这只是运气,还是至少就 Microsoft C++ 而言,它是否“格式良好”?

我知道我可以改用某种外部静态分析工具来做到这一点,但这与我想要的非常接近,主要优势在于它全部整合到被检查的程序中。

如果我可以为每个 T 调用 Initializer(看起来我可以),那么提取类型信息就很容易了。我可以使用 boost typeindex 或我需要的任何其他东西对模板参数进行字符串化。此处使用全局计数器仅用于查看是否正在创建 Initializer 实例。

【问题讨论】:

  • 糟糕,格式化问题。只是缺少一个花括号。我修复了它,所以它现在应该可以工作了。它至少在 Visual Studio 2017 中编译。
  • 看起来您真正的问题是如何收集有关在您的程序中实例化哪些模板变体的信息。我不知道确切的解决方案,但它可能与解析目标代码和/或使用一些专用工具来内省 AST(可能是基于 clang 的东西?)有关。
  • @VTT 好吧,我考虑了各种方法,包括对现有代码执行额外静态分析的外部程序(或 clang 插件),但我宁愿将类型提取合并到程序中本身。在这里我真的很好奇这段代码。不过,也欢迎任何有关如何以不同方式进行操作的建议。

标签: c++ templates initialization static-members


【解决方案1】:

如果您愿意为您的对象添加额外变量的成本,这似乎可以满足您的需求,尽管这一切都非常复杂,我可能会遗漏一个案例:

#include <iostream>

static int& gInt() { // Global counter
    static int i;
    return i;
}

struct Initializer {
    Initializer() { ++gInt(); }
};


template <class T>
struct Foo {
    Foo() { // This never gets called and we want that
        std::cout << "Constructed Foo!" << std::endl; 
    }
  private:
    static Initializer gint_incrementer;
    void* p_ = &gint_incrementer; // force existence 
};

template <typename T>
Initializer Foo<T>::gint_incrementer;

void unused() {
    Foo<char> c;
    Foo<char> c2;
    Foo<double> d;
    Foo<int> i;
    Foo<float> f;
}

int main() {
    std::cout << gInt() << std::endl; // prints 4
}

【讨论】:

  • 嗯,这比我想的要简单得多。有趣...我以为我以前尝试过类似的方法,但感觉它不起作用。显然我一定错过了一些东西,因为你的代码几乎是等价的并且没有黑客攻击。谢谢。
  • 是的,我现在记得了。我在 Initializer 中有一个虚拟函数 foo 并在 Foo 的构造函数中调用它。在这个简单的示例中,它与指针的作用相同,尽管我确信它在我正在处理的实际项目中不起作用。它没有生成 Initializer 定义,我收到了链接器错误。它可能是由多个翻译单元或其他原因引起的吗?我的想法不多了...
  • 没关系,我解决了。你的方法确实有效。最重要的是,我对 Foo 进行了部分专业化,但我忘记为它定义 Foo::gint_incrementer 。它必须像整个类本身一样重新定义。
  • 这使得计数器工作,但这如何帮助确定哪些模板变体被实例化?
  • @VTT 它没有,虽然这是在标题中,但代码或问题中没有任何内容让我认为这是 OP 真正想要的,但如果是这样,那就不是
【解决方案2】:

如果我们使用 schwartz counter 成语的变体,则无需任何技巧也无需 unused() 函数即可实现:

global_counter.hpp

#pragma once

struct GlobalCounter {
    int next();
    int value() const;
    int value_ = 0;
    struct init {
        init();
        ~init();
    };
};

extern GlobalCounter& globalCounter;
static GlobalCounter::init globalCounterInit;

global_counter.cpp

#include "global_counter.hpp"

#include <memory>
#include <type_traits>

static int globalCounterCount;
static std::aligned_storage_t <sizeof(GlobalCounter), alignof(GlobalCounter)> globalCounterStorage;
GlobalCounter& globalCounter = reinterpret_cast<GlobalCounter&>(globalCounterStorage);

GlobalCounter::init::init() {
    if(globalCounterCount++ == 0) new (&globalCounter) GlobalCounter ();
}

GlobalCounter::init::~init() {
    if (--globalCounterCount == 0) globalCounter.~GlobalCounter();
}

int GlobalCounter::next() {
    return value_++;
}

int GlobalCounter::value() const {
    return value_;
}

foo.hpp

#pragma once

#include "global_counter.hpp"
#include <iostream>

template <class T>
struct Foo {
    Foo() { // This never gets called and we want that
        std::cout << "Constructed Foo!" << std::endl;
    }

    static int ident;
};

template<class T>
int Foo<T>::ident;

template<class T>
struct EnableFoo
{
    EnableFoo()
    {
        if (counter++ == 0)
            Foo<T>::ident = globalCounter.next();
    }

    static int counter;
};
template<class T> int EnableFoo<T>::counter;



static EnableFoo<char> enableFooChar;
static EnableFoo<int> enableFooInt;
static EnableFoo<double> enableFooDouble;
static EnableFoo<float> enableFooFloat;

main.cpp

#include <iostream>
#include "foo.hpp"

int main() {

    std::cout << globalCounter.value() << std::endl; // prints 4
    return 0;
}

查看施瓦茨计数器:

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter

【讨论】:

    【解决方案3】:

    好的,因为我的问题中的解决方案只是部分的,我将分享一个工作代码 sn-p,它实际上显示了我所追求的,现在我想通了。

    我们不需要任何特定于编译器的 hack,以下应该通用:

    #include <iostream>
    #include <vector>
    #include <string>
    #include <mutex>
    #include <boost/type_index.hpp>
    
    static std::vector<std::string>& getInstantiations() {
        static std::vector<std::string> instantiations;
        return instantiations;
    }
    
    static std::mutex& getMutex() {
        static std::mutex mut;
        return mut;
    }
    
    template <class T>
    struct Introspection {
        Introspection() { 
            std::lock_guard<std::mutex> lock(getMutex());
            getInstantiations().push_back(boost::typeindex::type_id<T>().pretty_name());
        }
        void forceExistence() {}
    };
    
    
    template <class... Ts>
    struct Foo {
        Foo() { // This never gets called and we want that
            std::cout << "Constructed Foo!" << std::endl;
            introspection.forceExistence();
        }
    private:
        static Introspection<Foo<Ts...>> introspection;
    };
    
    template <class... Ts>
    Introspection<Foo<Ts...>> Foo<Ts...>::introspection;
    
    void unused() {
        Foo<char> c;
        Foo<char> c2;
        Foo<double> d;
        Foo<int, const int> i;
        Foo<float, bool, long> f;
    }
    
    int main() {
        for (auto& i : getInstantiations()) {
            std::cout << i << std::endl;
        }
    
        /*
            output:
    
            Foo<char>
            Foo<double>
            Foo<int, int const>
            Foo<float, bool, long>
        */
    }
    

    它看起来很傻,但考虑一个更大的项目,到处都是Foo&lt;...&gt; 声明。是的,也许我可以只使用正则表达式搜索,但这样我可以在被检查的程序运行时实际使用收集的信息。回显类型名称只是我们可以做的最简单的例子。

    【讨论】:

      猜你喜欢
      • 2010-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-02
      • 1970-01-01
      • 2012-02-11
      相关资源
      最近更新 更多