编译期间是不是可以生成常量值?

Posted

技术标签:

【中文标题】编译期间是不是可以生成常量值?【英文标题】:Is it possible to generate constant value during compilation?编译期间是否可以生成常量值? 【发布时间】:2010-06-14 20:19:34 【问题描述】:

我希望我的类通过唯一的哈希码来识别每种类型。但是我不希望每次方法都生成这些哈希值,例如。 int GetHashCode(),在运行时调用。我想使用已经生成的常量,我希望有一种方法可以让编译器进行一些计算并设置这些常量。可以使用模板完成吗?如果可能的话,你能给我举个例子吗?

更新:

感谢kriss' 的评论,我意识到我的问题应该是这样的: 如何以尽可能低的运行时成本进行类型检查?

我想根据类类型检查指向对象的指针。只是我在我的库中实现的类,所以我在考虑一些自定义散列,因此是原始问题。我确实考虑过使用typeid,但我不知道使用它的运行时成本。我做了一个假设,因为 typeid 产生了一个 type_info 类,它比简单地比较唯一的 int 值更消耗。

【问题讨论】:

可能在预编译器中有一些东西,虽然没有关于预编译器哈希函数和/或随机的具体想法 你认为你为什么需要这样做? 如果您的 GetHashCode() 方法返回一个常量并被内联,它不会在运行时真正被调用,但肯定会被编译器内联。没有运行时成本。不要费心寻找复杂的解决方法,只要相信编译器...... 在我的 Symbian 时代,我们曾经使用编译后构建工具将各种 ID 注入到 ROM 中;我们会喜欢预处理器可以完成的简单哈希系统,而不是尝试跟踪占位符并将它们外部化 你能显示你用来获取哈希码的界面吗? GetHashCode 是方法还是独立函数?您将已生成的常量保存在哪里 - 在类中或其他地方? 【参考方案1】:

您可以使用boost.MPL 来完成。

【讨论】:

我不想使用 boost,抱歉。我应该在我的问题中提到这一点。【参考方案2】:

我会走简单的路线:

对于将是静态属性的 - 所以只需为每个类选择一个数字。 对于实例 - 只需使用地址即可。

【讨论】:

我按照您的说法处理实例检查(某些情况除外),但是为每个类选择一个数字对我来说不是一个选项。我正在开发 3 个可以单独使用或合作的库,并且不想在每次创建类时检查一个数字在所有 3 个库中是否唯一。【参考方案3】:

静态常量在编译时进行评估——这几乎是整个元编程的基础。此外,type_info::hash_code 特别适合你的需求,所以试试 -

class MyClass

static const size_t TypeHashCode = typeid(MyClass).hash_code();
...

(我现在不在编译器周围,所以这可能需要一些改进。明天会尝试重新检查)

编辑:确实,它不仅是特定于 MS 的,而且仅在 VS2010 中添加 - 但是,至少 MS 同意这是一个有效的需求。如果您不允许在代码中同时使用 VS2010 boost - 您几乎只能使用符合标准的工具:typeid 或 dynamic_cast。它们确实会产生一些开销,但我会格外小心地验证这种开销确实是一场有价值的战斗。 (我的钱去 - 不是。)

【讨论】:

【参考方案4】:

所有这些类都有一些共同点。那么为什么不在一个公共枚举中为每个枚举添加一个符号常量,你将让枚举为你提供值,这比提供显式常量更容易(你仍然需要在枚举中声明每个类)。

【讨论】:

使用枚举是我目前的解决方案,但我想改变它,因为为了满足我的需要,这样的枚举应该可以在我的库之外扩展。【参考方案5】:
template<class T>
struct provide_hash_code_for_class

  public:
    static uintptr_t GetHashCode()
    
      return(reinterpret_cast<uintptr_t>(&unused));
    
  private:
    static void *unused;
;

template<class T>
void *provide_hash_code_for_class<T>::unused;

class MyClass : public provide_hash_code_for_class<MyClass>

;

int main()

  std::cout << std::hex << MyClass::GetHashCode() << std::endl;
  std::cout << std::hex << MyClass().GetHashCode() << std::endl;
  return(0);

请注意,哈希码在运行之间会发生变化,因此您不能依赖它们来进行进程间通信。

【讨论】:

从 MyClass 派生的类与基类具有相同的哈希码,这是不可接受的。使用 provide_hash_code_for_class 导致编译错误 C2385: 'GetHashCode' 的模糊访问。 这很好。您可能可以在您正在编写的课程中使用 'using provide_hash_code_for_class::GetHashCode' 来消除符号的歧义,但是输入太多了...而且我猜 trait type class 是不可能的,因为您可能不想改变界面?【参考方案6】:

在 Nikolai N Fetissov 的 simple route 路线上建造:

对于将是静态属性的类 - 使用转换为 intptr_t 的函数地址来提供唯一但已编译的值。 例如 - 只需使用地址。

【讨论】:

【参考方案7】:

遗憾的是没有标准支持的编译时类型hash_code。作为一种解决方法,可以从类名生成编译时哈希。下面是一个例子。

#include <stdint.h>
#include <string>
#include <vector>
#include <iostream>
#include <memory>
#include <cassert>

//Compile-time string hashing.
class HashedString

public:
    typedef int64_t HashType;
    explicit constexpr HashedString(const char* str):  m_hash(hashString(str)) 

    static inline constexpr HashType hashString(const char* str)
    
        return ( !str ? 0 : hashStringRecursive(5381, str));
    
    static inline constexpr HashType hashStringRecursive(HashType hash, const char* str)
    
        return ( !*str ? hash : hashStringRecursive(((hash << 5) + hash) + *str, str + 1));
    
    const HashType m_hash;
;

struct EventBase

    using IdType = HashedString::HashType;

    virtual ~EventBase() 
    IdType getId() const   return m_eventId;         //present the runtime event id

    EventBase(IdType myId) : m_eventId  myId   

    template<class DerivedEvent>
    const DerivedEvent* getAs() const
    
        return dynamic_cast<const DerivedEvent*>(this);
    

protected:
    const IdType m_eventId;
;

#define DEFINE_EVENT_ID(className) \
static constexpr IdType id = HashedString(#className).m_hash;   \

struct SomeEvent1 : public EventBase

    DEFINE_EVENT_ID(SomeEvent1);

    SomeEvent1(int status) : EventBase(id), m_status  status   assert(id == m_eventId);  
    int m_status;
;


struct SomeEvent2 : public EventBase

    DEFINE_EVENT_ID(SomeEvent2);

    SomeEvent2() : EventBase(id)  assert(id == m_eventId); 
    std::string m_s = "test event 2";
;

void testEvents()

    std::vector<std::shared_ptr<EventBase>> events;

    events.push_back(std::make_shared<SomeEvent1>(123));
    events.push_back(std::make_shared<SomeEvent2>());

    for (auto event : events) 
        switch(event->getId()) 
            case SomeEvent1::id:
                std::cout << "SomeEvent1 " << event->getAs<SomeEvent1>()->m_status << std::endl;
                break;
            case SomeEvent2::id:
                std::cout << "SomeEvent2 " << event->getAs<SomeEvent2>()->m_s << std::endl;
                break;
        
    

【讨论】:

以上是关于编译期间是不是可以生成常量值?的主要内容,如果未能解决你的问题,请参考以下文章

当任何数学运算在 .net 4 中产生“NaN”时,如何强制 C# 编译器抛出异常?

为啥在编译期间不使用 GCC 选项 -Os?

在使用 github 操作进行构建期间执行诗歌安装时,使用预编译的 numpy 包而不是构建它

Makefile编译时怎么打印出变量值

gcc 编译器标志在编译期间抑制模板扩展的模板错误?

如何在编译期间指示 Emscripten 应该在哪里找到源文件?