【问题标题】:restricting c++ template usage to POD types将 C++ 模板的使用限制为 POD 类型
【发布时间】:2013-10-09 20:23:36
【问题描述】:

我有一个 c++ 模板类,它只有在模板化类型是普通旧数据时才能正确运行。任何带有构造函数的东西都不能正常工作。

当有人尝试这样做时,我想以某种方式获得编译时或运行时警告。

//this should generate error
myclass<std::string> a;

//this should be fine
myclass<int> b;

有什么诀窍吗?

【问题讨论】:

    标签: c++ templates typetraits


    【解决方案1】:
    #include <type_traits>
    
    template<typename T>
    class myclass
    {
        static_assert(std::is_pod<T>::value, "T must be POD");
    
        // stuff here...
    };
    

    如果你传递一个非POD类型作为模板参数,上面会导致编译错误。此解决方案要求 &lt;type_traits&gt; 标头和 static_assert 关键字使用 C++11。

    编辑:如果您的编译器支持 TR1(大多数都支持),您也可以在 C++03 中实现它:

    #include <tr1/type_traits>
    
    template<typename T>
    class myclass
    {
        static char T_must_be_pod[std::tr1::is_pod<T>::value ? 1 : -1];
    
        // stuff here...
    };
    

    【讨论】:

    • 值得一提的是它只在 c++11 中。
    • 不错,在 40 秒内获得 5 票。是的,它是 C++11,但别无选择。 is_pod 映射到编译器内部;你不能写你的。
    • @Potatoswatter 它也可以用 C++03 编写,也可以使用 std::tr1::is_pod,但它位于哪个标头中并没有明确指定。
    • @Potatoswatter,你也可以使用boost::is_pod
    • @julien 简单地说,按照您的链接:“如果没有编译器的一些(尚未指定)帮助,is_pod 将永远不会报告类或结构是 POD;这总是安全的,如果可能是次优的。”因此,它提供了对各种不同的、预标准的、不受支持的功能的访问。如果不存在合适的特征,则盲目返回false_type
    【解决方案2】:

    如果你有 C++11 支持,std::is_pod 应该能满足你的需要。将它与 std::enable_if 或标签调度一起使用。例如这样的:

    template <typename T, typename Enable = void>
    class Test;
    
    template<typename T>
    class Test<T, typename std::enable_if<std::is_pod<T>::value, void>::type>
    {};
    
    int main() {
        Test<int> t1;
        //Test<std::string> t2; <-this will not compile
    }
    

    【讨论】:

      【解决方案3】:

      虽然static_assert 在大多数情况下可能就足够了,但使用enable_if 和标签分派通过SFINAE 的方式为您的班级用户提供了更大的灵活性。考虑:

      #include <type_traits>
      #include <string>
      #include <iostream>
      template <typename T,
          class=typename std::enable_if< std::is_pod<T>::value >::type>
      struct myclass
      {
          typedef T value_type;
          T data;
      };
      
      template <typename T>
      void enjoy(T)
      {
          std::cout << "Enjoying T!" << std::endl;
      }
      
      template <typename T>
      void enjoy(typename myclass<T>::value_type)
      {
          std::cout << "Enjoying myclass<T>::value_type!" << std::endl;
      }
      
      int main()
      {
          enjoy<int>(int()); // prints: Enjoying myclass<T>::value_type!
          enjoy<std::string>(std::string()); // SFINAE at work - prints: enjoying T!
          myclass<int> i; // compiles OK
          //myclass<std::string> s; // won't compile - explicit instantiation w/non-POD!
      }
      

      现在,如果您从 myclass 定义中删除第二个模板参数,而是像其他人建议的那样,添加一个

        static_assert(std::is_pod<T>::value, "POD expected for T");
      

      在类内部,main() 中的第二行将无法编译,触发 static_assert。

      也就是说,来自static_assert 的错误比来自失败的enable_if 的错误对人类观察者更友好。所以,如果static_assert 适合你,那就去吧。否则,如果您确实需要对班级周围的通用编程更加友好,请考虑在 enable_if 周围添加解释性评论:

       // POD expected for T
       class=typename std::enable_if< std::is_pod<T>::value >::type>
      

      除非你周围的每个人都精通 C++11。

      在现实生活中,最好解释一下为什么对于static_assert 和评论文本,T 必须是 POD。

      【讨论】:

        【解决方案4】:

        如果你没有 C++11

        如果目标 POD 类型有限(intfloat、...),您可以将实现放入 .cpp 文件并为这些类型显式实例化它:

        .h文件:

        template <typename T>
        class myclass
        {
            T data;
        public:
            void func();
        };
        

        .cpp文件:

        #include "myclass.h"
        
        template <typename T>
        void myclass<T>::func()
        {
        }
        
        template class myclass<float>;
        template class myclass<int>;
        template class myclass<char>;
        ...
        

        之后,myclass 仅可用于这些类型,而用于其他类型。

        【讨论】:

        • POD类型不限,struct foo { int bar;浮动 baz; };是 POD 类型
        • @paulm: 这就是我写 "如果目标 POD 类型有限..." 的原因
        • +1 更多参考explicit instantiation
        【解决方案5】:

        将不推荐将Simple's 的答案更新为newer C++20 standard changesstd::is_pod&lt;T&gt;。由于这是谷歌这个主题中最明显的回应,让我描述一下其他人的不同之处,这些人会来这里寻找最新的答案。

        POD type 被引入作为普通旧数据的定义 - 相当于 C 结构。从 C++11 到 C++20 对 POD 的要求是:

        • 是普通类型
          • 移动/复制/默认构造函数是 trivial 或已删除并且至少存在一个。
          • 移动/复制赋值运算符也是如此
          • 所有成员(也是继承的)都是微不足道的
        • 是标准布局类型
          • 没有引用类型的成员
          • 没有虚拟基类(允许非虚拟继承)
          • 没有虚函数
          • 所有成员都具有相同的访问类型(公共/受保护/私有)
          • 所有成员(也被继承)都是标准布局类型

        对于那些不太了解用法之间区别的人,这里是经验法则。

        • std::is_trivial 当你计划在你的对象上进行内存复制/移动时,应该检查。它保证该对象的内存副本将创建精确的副本,不需要构造或解构。您可以分配内存并粘贴从套接字接收的内容。这是通过套接字传输数据或将它们存储在通用缓冲区中的基本用法。
        • std::is_standard_layout 保证不同 C++ 标准之间的兼容性,因为有关内存对齐的规则随着时间的推移而改变,并且某些实现可能使用由一个标准版本保证的特性,而在另一个标准版本上放宽。差异与内存排序限制有关,例如每个下一个成员应该有更高的内存地址,或者第一个成员应该有整个结构的地址。

        【讨论】:

          【解决方案6】:

          使用 type_traits 和 static_assert,这很容易:

          #include <type_traits>
          
          struct A{
          };
          struct B{
              virtual ~B(){}
          };
          
          template< class T >
          struct MyClass
          {
              static_assert( std::is_pod<T>::value, "not a POD" );
          };
          
          int main()
          {
              MyClass<A> a;
              //MyClass<B> b; -- break, cause not a POD
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-03-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-06-07
            • 1970-01-01
            相关资源
            最近更新 更多