5

union成员可能没有析构函数或构造函数。因此,如果有构造函数Foo,我不能自己模板化以下类:MyClassMyClass

template<class T>
struct Foo {
  T val;
  Foo(T val_) : val(val_) {}
  size_t hash() const {
    union {T f; size_t s;} u = { val };
    return u.s;
  }
};
struct MyClass {
  bool a;
  double b;
  MyClass(bool a_, double b_) : a(a_), b(b_) {}
};

如果我仍然这样做,我会收到此错误:

member 'MyClass Foo<T>::hash() const 
[with T = MyClass]::<anonymous union>::f' with constructor 
not allowed in union

为了解决这个问题,我创建MyClass了一个笨拙的构造函数,它首先复制周围的东西:

struct MyClass {
  bool a;
  double b;
};
MyClass createMyClass(bool a, double b) {
  MyClass m;
  m.a = a;
  m.b = b;
  return m;
}

但我想知道是否有比使用此createMyClass功能更好的方法。构造函数会更高效,并且作为关键组件MyClassFoo<MyClass>在我的代码中被构造了数百万次。

标准::对

我也有点惊讶它可以用于std::pairunion

Foo<std::pair<bool, double> > f2(std::make_pair(true, 3.12));

据我所知,std::pair见代码)有一个构造函数?

4

4 回答 4

10

编辑: 我最初的立场std::pair是错误的,工会不应该允许这样做。要使一个类成为联合的有效成员,它必须具有根据标准 9.5.1 的普通构造函数。从第 12.1.5 段开始,普通构造函数的定义是这样的:

如果类 X 没有用户声明的构造函数,则隐式声明默认构造函数。隐式声明的默认构造函数是inline public其类的成员。 如果构造函数是隐式声明的默认构造函数并且满足以下条件,则构造函数是微不足道的:

  • 它的类没有虚函数,也没有虚基类,并且
  • 其类的所有直接基类都有普通的构造函数,并且
  • 对于其类的所有属于类类型(或其数组)的非静态数据成员,每个这样的类都有一个简单的构造函数

第 20.2.2.2 段规定以下构造函数必须成对可用:

pair(const T1& x, const T2& y);

一旦提供此构造函数,就不会隐式声明默认构造函数。

有趣的是,我的编译器(Visual Studio 2008)似乎给予了std::pair特殊处理。如果我从实现中复制代码std::pair并将其放在我自己的命名空间 foo 中,则联合不起作用:)

namespace foo {
    template<class _Ty1, class _Ty2> struct pair {
        typedef _Ty1 first_type;
        typedef _Ty2 second_type;
        pair() : first(_Ty1()), second(_Ty2()) {
        }
    }
}

//This doesn't work in VC2008
union Baz {
    foo::pair<bool, double> a;
    int b;
}
//This works in VC2008
union Buz {
    std::pair<bool, double> a;
    int b;
}

您的解决方案是解决此问题的常用方法。我通常在类名前面加上一个C(构造的缩写)来部分模仿普通的构造函数语法,在你的情况下这将变成CMyClass(a, b).

正如史蒂夫和马蒂厄指出的那样,尽管您没有使用非常好的散列函数。首先,没有真正的保证(我认为,如果我错了,请纠正我),f并且s在联合中甚至会部分占用相同的内存空间,其次,即使它们在实践中可能会共享第一个min(sizeof(s), sizeof(f))字节,这意味着对于MyClass你只是对部分价值进行散列。在这种情况下,您将对 的值进行散列bool a,在这种情况下,有两个选项:

  1. 您的编译器将int其用作内部表示,bool在这种情况下,您的哈希函数将仅返回两个值,一个为真,一个为假。
  2. 您的编译器char用作bool. 在这种情况下,该值可能会被填充到至少sizeof(int),在这种情况下您的情况与 1 相同。或者在MyClass分配时使用堆栈上的任何随机数据,这意味着您将获得相同输入的随机哈希值.

如果您需要按 I 的整个值进行散列,T我会将数据复制到史蒂夫建议的临时缓冲区中,然后使用此处讨论的可变长度散列函数之一。

于 2009-12-11T14:15:20.107 回答
4

我会替换这个:

size_t hash() const {
    union {T f; size_t s;} u = { val };
    return u.s;
}

有了这个:

size_t hash() const {
    size_t s = 0;
    memcpy(&s, &val, std::min(sizeof(size_t), sizeof(T)));
    return s;
}

复制两个大小中的较小者而不是较大者,如果 memcpy 是编译器的内在特性,那么您正在寻找优化。不过,最重要的是,构造函数 T 有什么并不重要。

但是,如果 T 是一个大类型,它就不是一个好的散列函数。在您的示例 MyClass 中,您可能会发现bool并且size_t在您的实现中具有相同的大小,因此 double 根本不参与散列,因此只有两个可能的散列值。

不过,情况可能会更糟。如果 T 有任何虚函数,您可能会发现所有实例的哈希值都相同:vtable 的地址...

于 2009-12-11T17:56:24.000 回答
2

关于使用 anstd::pair作为工会成员,我认为应该禁止。该标准说(§12.1):

联合成员不应属于具有非平凡构造函数的类类型(或其数组)。

因此,任何具有用户定义构造函数的类都不能在联合中使用,因为将不再隐式声明默认构造函数。现在在std::pair(第 20.2.2 节)的规范中,明确指出对实现必须提供参数化构造函数来初始化两个值。因此,您使用的 pair 实现或 union 实现都不符合标准。

注意:测试您在 Comeau 上提供的代码会出现以下错误:

"ComeauTest.c", line 8: error: invalid union member -- class
          "std::pair<bool, double>" has a disallowed member function
      union {T f; size_t s;} u = { val };
               ^
          detected during instantiation of "unsigned int Foo<T>::hash() const
                    [with T=std::pair<bool, double>]" at line 22
于 2009-12-11T14:35:20.713 回答
2

我只有一个问题:为什么要使用 union ?

据我了解,哈希应该对应于对象的前几个字节。如果你要这样做,为什么不:

size_t hash() const {
  return reinterpret_cast<size_t>(val);
}

sizeof(T)这应该以更高的效率完成相同的技巧(我认为),因为堆栈上没有分配大小的对象。

于 2009-12-11T15:53:23.970 回答