首页 > 编程语言 >C++11平凡可复制类型检测is_trivially_copyable

C++11平凡可复制类型检测is_trivially_copyable

时间:2024-07-16 15:45:08浏览次数:14  
标签:11 std copyable trivially value 类型 平凡

1.C++基础回顾
         在C++11中,平凡类型(Trivial Type)、平凡可复制类型(TrivialCopyable)、标准布局类型(Standard-layout Type)是描述类在内存中布局特性的术语,它们与类的构造、拷贝、赋值和销毁行为有关,也影响着类的内存布局和对齐方式。下面用通俗的语言解释这些概念:

1.1.平凡类型 
        指那些在内存中的行为非常简单的类。它们的构造函数、析构函数、拷贝构造函数和赋值运算符都没有自定义实现,完全由编译器提供的默认行为即可,而且也不能包含虚函数以及是虚基类的父类, 这意味着这些类的对象可以像基本数据类型一样被创建和销毁,不需要特殊的资源管理代码。

        以下是平凡类型和非平凡类型的示例代码展示,参考代码如下:

#include <iostream>
 
// 平凡类型:没有任何自定义的构造函数、析构函数、拷贝控制成员
struct TrivialType {
    int a;
    double b;
};
 
// 非平凡类型:至少有一个自定义的特殊成员函数
struct NonTrivialType1 {
    int a;
    double b;
 
    // 自定义构造函数
    NonTrivialType1() : a(0), b(0.0) {}
 
    // 自定义拷贝赋值运算符
    NonTrivialType1& operator=(const NonTrivialType1& other) {
        a = other.a;
        b = other.b;
        return *this;
    }
 
    // 自定义析构函数
    ~NonTrivialType1() {
        std::cout << "NonTrivialType1 destroyed\n";
    }
};
 
//使用=default关键字可以显式地声明默认的构造函数,从而使得类型恢复 “平凡化”。
struct TrivialType2 {
    int a;
    double b;
 
    // 自定义构造函数
    TrivialType2() : a(0), b(0.0) {}
 
    TrivialType2() = default;
};
 
int main() {
    TrivialType t1, t2;
    t2 = t1; // 平凡类型的赋值操作是平凡的
 
    NonTrivialType nt1, nt2;
    nt2 = nt1; // 非平凡类型的赋值操作不是平凡的
 
    std::cout << "TrivialType is trivially:" 
              << std::is_trivially<TrivialType>::value << std::endl; //输出:true
    std::cout << "NonTrivialType1 is trivially:" 
              << std::is_trivially<NonTrivialType1>::value << std::endl; //输出:false
 
    std::cout << "TrivialType2is trivially:" 
              << std::is_trivially<TrivialType2>::value << std::endl; //输出: false
    return 0;
}

        在这个示例中:

        TrivialType 是一个平凡类型,因为它没有任何自定义的特殊成员函数。它的构造、拷贝、移动、赋值和析构操作都是由编译器提供的默认实现。
        NonTrivialType1 是一个非平凡类型,因为它至少有一个自定义的特殊成员函数(在这个例子中是构造函数、拷贝赋值运算符和析构函数)。这意味着它至少有一个操作不能由编译器提供的默认实现来完成。

        TrivialType2虽然重新定义了构造函数,但是使用=default,使用=default关键字可以显式地声明默认的构造函数,从而使得类型恢复 “平凡化”。

注意事项:

即使类没有显示定义特殊成员函数,如果类中有虚函数或虚基类,它也不是平凡类型。
类中如果有动态内存分配(如指针成员)或需要特殊资源管理的成员,也不是平凡类型。
平凡类型的所有特殊成员函数都是平凡的,这意味着它们可以没有函数体(即使用编译器提供的默认实现)。
        平凡类型在C++中很重要,因为它们可以提高效率,允许编译器进行更多的优化。例如,平凡类型的拷贝和赋值可以通过简单的内存复制完成,而不需要调用任何成员函数。

1.2.平凡可复制类型
        是平凡类型的一个扩展,它不仅包括所有平凡类型,还包括那些可以安全地被复制和移动的类型,即使这些类型不是平凡类型。例如,一个类可能有一个自定义的构造函数,但如果它保证对象的内容可以通过简单的位拷贝(bitwise copy)来复制,那么它也可以被认为是平凡可复制的。它必须满足两个条件:

类型可以被复制或移动,且不需要特殊的资源管理。
类型的所有特殊成员函数(构造函数、拷贝构造函数、移动构造函数、赋值运算符、移动赋值运算符和析构函数)都是平凡的或者被删除的(deleted)。
下列类型统称为可平凡复制类型:

标量类型
可平凡复制类类型
上述类型的数组
这些类型的有 cv 限定版本
说明:        

        一般来说,对于任何可平凡复制类型 T 及 T 对象 obj1,能复制 obj1 的底层字节到 char 或 unsigned char 或 std::byte (C++17 起) 的数组中,或到 T 的另一不同对象 obj2 中。obj1 与 obj2 均不可为潜在重叠的子对象。

        如果复制 obj1 的底层字节到这种数组中,然后复制结果内容回 obj1 中,那么 obj1 将保有其原值。如果复制 obj1 的底层字节到 obj2 中,那么 obj2 将保有 obj1 的值。

底层字节能由 std::memcpy 或 std::memmove 复制,只要不访问存活的 volatile 对象即可。

        具体示例我们将在后面给出。

1.3.标准布局类型
        指那些在内存布局上满足一定规则的类。这些规则包括所有非静态成员的访问权限必须相同,类不能有虚函数或虚基类,且所有基类也必须是标准布局类型。标准布局类型的一个重要特性是它们的内存布局在不同的编译器和平台上是一致的,这对于跨平台的二进制数据交换非常重要,它必须满足以下条件:

        1)类型的所有非静态数据成员都是公共的(public)。
        2)类型不包含虚函数、虚基类或非标准布局的基类。
        3)类型的所有基类都是标准布局类型。
        4)类型不包含动态内存分配,如没有指向其自身类型的指针成员。
        5)类型的所有数据成员的访问权限(public、protected、private)都是相同的。

示例代码如下:

#include <iostream>
#include <type_traits> // For std::is_standard_layout
 
// 标准布局类型:没有任何虚函数或虚基类,所有数据成员都是公共的
struct StandardLayoutType {
    int a;
    double b;
};
 
// 非标准布局类型:包含虚函数
struct NonStandardLayoutTypeWithVirtualFunction {
    virtual void dummy() {}
    int a;
    double b;
};
 
// 非标准布局类型:包含非标准布局基类
struct NonStandardBase {
    int a;
protected:
    double b; // Data member with non-public access
};
 
struct NonStandardLayoutTypeWithNonStandardBase : NonStandardBase {
    int c;
};
 
// 标准布局类型:尽管有继承,但基类是非虚继承且本身也是标准布局
struct StandardLayoutTypeWithInheritance : StandardLayoutType {
    char c;
};
 
int main() {
    std::cout << std::boolalpha; // Print bool values as true/false
 
    // 检查是否为标准布局类型
    std::cout << "Is StandardLayoutType standard layout? " << std::is_standard_layout<StandardLayoutType>::value << std::endl;
    std::cout << "Is NonStandardLayoutTypeWithVirtualFunction standard layout? " << std::is_standard_layout<NonStandardLayoutTypeWithVirtualFunction>::value << std::endl;
    std::cout << "Is NonStandardLayoutTypeWithNonStandardBase standard layout? " << std::is_standard_layout<NonStandardLayoutTypeWithNonStandardBase>::value << std::endl;
    std::cout << "Is StandardLayoutTypeWithInheritance standard layout? " << std::is_standard_layout<StandardLayoutTypeWithInheritance>::value << std::endl;
 
    return 0;
}

在这个示例中:

a) StandardLayoutType 是一个标准布局类型,因为它没有任何虚函数或虚基类,所有数据成员都是公共的。
b) NonStandardLayoutTypeWithVirtualFunction 不是标准布局类型,因为它包含一个虚函数。
c)NonStandardLayoutTypeWithNonStandardBase 不是标准布局类型,因为它有一个基类 NonStandardBase,该基类包含受保护的成员,不符合所有数据成员都是公共的规则。
d)StandardLayoutTypeWithInheritance 是一个标准布局类型,尽管它继承StandardLayoutType,但继承是不带虚函数的,且所有数据成员都是公共的。

2.std::is_trivially_copyable
2.1.定义
它是在标头 <type_traits> 定义

template< class T >
struct is_trivially_copyable;
主要用来判断T是否平凡可复制类型。

并非非潜在重叠子对象的可平凡复制类型的对象,是仅有的能以 std::memcpy 安全复制或以 std::ofstream::write() / std::ifstream::read() 序列化自/到二进制文件的 C++ 对象。

2.2.使用
示例1:

#include <type_traits>
 
struct A { int m; };
static_assert(std::is_trivially_copyable_v<A> == true);
 
struct B { B(B const&) {} };
static_assert(std::is_trivially_copyable_v<B> == false);
 
struct C { virtual void foo(); };
static_assert(std::is_trivially_copyable_v<C> == false);
 
struct D
{
    int m;
 
    D(D const&) = default; // -> 可平凡复制
    D(int x) : m(x + 1) {}
};
static_assert(std::is_trivially_copyable_v<D> == true);
 
int main() {}

在这个示例中:

        1) A是一个平凡可复制类型,因为它没有自定义的特殊成员函数,且可以被简单地复制和移动。
        2) B有一个自定义的拷贝构造函数,所以它不是平凡可复制的。尽管它的赋值操作可能是平凡的,但拷贝构造函数的存在使得整个类型不是平凡可复制的。
        3) C有一个虚函数,这使得它即使没有自定义的特殊成员函数,也不是平凡可复制的。虚函数的存在意味着类型需要有虚函数表(vtable),这违反了平凡可复制类型的定义。
        4) D虽然有一个自定义的拷贝构造函数,但是有一个使用=default的构造函数,所以它也是平凡可复制的。
        平凡可复制类型在C++中很重要,因为它们可以被编译器优化为没有额外开销的位拷贝操作,这对于性能敏感的程序是非常有益的。

示例2:

#include <iostream>
using namespace std;
 
// trivially copyable
class A
{
    ~A() = default;                    // trivially copyable
    A() {}                             // trivially copyable
    A(const A &) = default;            // trivially copyable
    A(A &&) = default;                 // trivially copyable
    A &operator=(const A &) = default; // trivially copyable
    A &operator=(A &&) = default;      // trivially copyable
};
 
class B
{
    // 只要有任意自定义的下列行为即会变成 not trivially copyable
    virtual void foo() = 0; // not trivially copyable
    // ~B() = delete;             // not trivially copyable
    // ~B() {}                    // not trivially copyable
    // B(const B &) {}            // not trivially copyable
    // B(B &&) {}                 // not trivially copyable
    // B &operator=(const B &) {} // not trivially copyable
    // B &operator=(B &&) {}      // not trivially copyable
};
 
// not trivially copyable
class C : public B
{
};
 
// trivially copyable
class D
{
public:
    explicit D(int val) : d(val) {}
    int d;
};
 
void TriviallyCopyableTest()
{
    cout << std::is_trivially_copyable<bool>::value << endl;           // trivially copyable
    cout << std::is_trivially_copyable<char>::value << endl;           // trivially copyable
    cout << std::is_trivially_copyable<int>::value << endl;            // trivially copyable
    cout << std::is_trivially_copyable<float>::value << endl;          // trivially copyable
    cout << std::is_trivially_copyable<double>::value << endl;         // trivially copyable
    cout << std::is_trivially_copyable<std::nullptr_t>::value << endl; // trivially copyable
    cout << std::is_trivially_copyable<int *>::value << endl;          // trivially copyable
    cout << std::is_trivially_copyable<A>::value << endl;              // trivially copyable
    cout << std::is_trivially_copyable<A *>::value << endl;            // trivially copyable
    cout << std::is_trivially_copyable<B>::value << endl;              // not trivially copyable
    cout << std::is_trivially_copyable<B *>::value << endl;            // trivially copyable
    cout << std::is_trivially_copyable<C>::value << endl;              // not trivially copyable
    cout << std::is_trivially_copyable<string>::value << endl;         // not trivially copyable
}

分析方法同上,我们在这里就不赘述了。

2.3.总结
        在 C++11 及其之后的版本中,如果一个类型是可平凡复制的,那么你可以安全地通过 memcpy 或 memmove 等函数进行复制,而不需要担心可能的副作用(如析构函数的调用或虚函数的重新定向等)。然而,你应该注意,即使一个类型是可平凡复制的,也并不意味着你应该总是使用 memcpy 来进行复制;在许多情况下,使用赋值操作符或复制构造函数是更安全、更清晰的选择。

推荐阅读

可平凡复制类型

std::is_trivially_copyable

C++之std::is_pod(平凡的数据)
                        
原文链接:

#include <iostream>
using namespace std;

// trivially copyable
class A
{
    ~A() = default;                    // trivially copyable
    A() {}                             // trivially copyable
    A(const A &) = default;            // trivially copyable
    A(A &&) = default;                 // trivially copyable
    A &operator=(const A &) = default; // trivially copyable
    A &operator=(A &&) = default;      // trivially copyable
};

class B
{
    // 只要有任意自定义的下列行为即会变成 not trivially copyable
    virtual void foo() = 0; // not trivially copyable
    // ~B() = delete;             // not trivially copyable
    // ~B() {}                    // not trivially copyable
    // B(const B &) {}            // not trivially copyable
    // B(B &&) {}                 // not trivially copyable
    // B &operator=(const B &) {} // not trivially copyable
    // B &operator=(B &&) {}      // not trivially copyable
};

// not trivially copyable
class C : public B
{
};

// trivially copyable
class D
{
public:
    explicit D(int val) : d(val) {}
    int d;
};

void TriviallyCopyableTest()
{
    cout << std::is_trivially_copyable<bool>::value << endl;           // trivially copyable
    cout << std::is_trivially_copyable<char>::value << endl;           // trivially copyable
    cout << std::is_trivially_copyable<int>::value << endl;            // trivially copyable
    cout << std::is_trivially_copyable<float>::value << endl;          // trivially copyable
    cout << std::is_trivially_copyable<double>::value << endl;         // trivially copyable
    cout << std::is_trivially_copyable<std::nullptr_t>::value << endl; // trivially copyable
    cout << std::is_trivially_copyable<int *>::value << endl;          // trivially copyable
    cout << std::is_trivially_copyable<A>::value << endl;              // trivially copyable
    cout << std::is_trivially_copyable<A *>::value << endl;            // trivially copyable
    cout << std::is_trivially_copyable<B>::value << endl;              // not trivially copyable
    cout << std::is_trivially_copyable<B *>::value << endl;            // trivially copyable
    cout << std::is_trivially_copyable<C>::value << endl;              // not trivially copyable
    cout << std::is_trivially_copyable<string>::value << endl;         // not trivially copyable
}

C++11 可平凡复制类型 (TriviallyCopyable) 

标签:11,std,copyable,trivially,value,类型,平凡
From: https://www.cnblogs.com/tryst/p/18305347

相关文章

  • Mike11闸门中Close和Unchanged的区别
    前言:近期研究了一个简单的闸门调度方式。调度方式设置一:闸门上游水位(Hups)达到某个水位时,闸门打开,否则关闸门即:闸门上游水位(Hups)达到某个水位时,闸门打开,否则关闸门。根据以上调度要求设置了如下图的参数:通过以上的结果,各位不难看出,闸门是不断的开关的,当水位高则开,水位......
  • 《火影忍者:究极风暴4》DX11报错令游戏崩溃怎样处理,火影忍者究极风暴4DX11报错游戏崩溃
    《火影忍者:究极风暴4》DX11报错致使游戏崩溃,这可让玩家们犯了难。究竟该如何解决这个棘手的问题呢?本篇文章将为大家带来火影忍者究极风暴4DX11报错游戏崩溃解决办法,感兴趣的小伙伴们一起来看看吧,希望能够帮助到大家。DX11报错游戏崩溃解决办法1、图形驱动更新:访问NVIDIA、A......
  • 前端后花园周刊vol.19-ESLint的下一个11年计划
    ⚡️行业动态ESLint的下一步计划:ESLint已有11年历史,它正在为下一个11年做准备,继续发展成为一种与语言无关的linter,任何人都可以为其编写插件。ESLint9.0中引入的新配置系统“只是重大变革的开始”。加速JavaScript生态系统:隔离声明TypeScript的新隔离声明功能......
  • Day11(二叉树) | 二叉树的递归遍历 二叉树的迭代遍历 二叉树的统一迭代法 二
    二叉树的递归遍历终于来到了递归!!!递归是进入动态规划的第一步,有部分的递归完全可以写成动态规划!这里可以移步到左程云的视频观看.递归的步骤:确定递归函数的参数和返回值:确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数,并且还要明确每次递归的返......
  • 题解:SP11469 SUBSET - Balanced Cow Subsets
    SP11469(折半搜索)题目大意:给出$N$个数,求可以被分成两个和相等的集合的子集数。思路分析:首先考虑朴素的DFS,每个数有三种情况:选为$A$集合,选为$B$集合,两个集合都不选。暴力DFS时间复杂度为$3^{20}$。观察到$N$很小,而$3^{10}$是可以通过本题的,于是考虑折半搜索。我......
  • [Windows] 号称最快免费小巧的远程桌面 AnyDesk v8.0.11单文件版
    描述对于经常在互联网上进行操作的学生,白领等!一款好用的软件总是能得心应手,事半功倍。今天给大家带了一款高科技软件虽然QQ拥有远程协助功能,但很多时候连接并不够流畅,而且被控电脑那方也必须要有人操作才行。因此,很多人会选择TeamViewer、AnyDesk等…这样更为专业的......
  • Spring Boot Vue 毕设系统讲解 11【协同过滤方法教学】
    目录1.基础知识用户基于的协同过滤(User-basedCF)物品基于的协同过滤(Item-basedCF)优缺点实际应用2.项目功能实战1.基础知识协同过滤(CollaborativeFiltering,CF)是一种广泛应用于推荐系统中的算法,它通过分析和利用用户与物品之间的交互信息来发现用户可能感兴趣......
  • [1.11b]暗黑破坏神2带自动开荒的kolbot正式发布!
    注意事项:一、game.exe1.11b版的game.exe制作方法请见本博客的《暗黑破坏神2在1.12a之前各版本的免CD的game.exe建立方法》经过我的测试,如果是下载ThePhrozenKeep论坛的GalaXyHaXz在DiabloII1.09bNoCdCodeedits-ThePhrozenKeep(d2mods.info)帖子里提供的1.12a前......
  • 【408真题】2011-25
    “接”是针对题目进行必要的分析,比较简略;“化”是对此题型的解题套路总结,并结合历年真题或者典型例题进行运用。涉及到的知识全部来源于王道各科教材(2025版)(408神功练成中……)文章目录一、接:本题分析二、化:套路总结一、接:本题分析2011-25分析【答】D【解......
  • 【408真题】2011-28
    “接”是针对题目进行必要的分析,比较简略;“化”是对此题型的解题套路总结,并结合历年真题或者典型例题进行运用。涉及到的知识全部来源于王道各科教材(2025版)(408神功练成中……)文章目录一、接:本题分析二、化:套路总结一、接:本题分析2011-28分析【答】D【解......