拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO

C++ 从&到&&

白鹭 - 2022-02-13 1950 0 0

人类发展史,就是不断挖坑、填坑的程序,
语言发展史也是如此!
任何一门设计合理的语言,给你的限制或提供的什么特性,都不是没有代价的,

C的指标

指标:pointer
指标的思想起源于汇编,指标思想是编程思想历史上的重大飞跃,
每一个编程语言都使用指标,C语言将指标完全暴露给了用户,潘多拉之盒,

使用指标的必要性:资源管理,即地址管理,

思想层:将地址包了一层,
语法层:T *p; *p;
编译器:包含一个intptr_t型别成员的结构体,
汇编层:暂存器间接寻址MOV,

image

C语言中只有一种自变量传递方式:值传递,
void f(int p)
void f(int *p)

利用指标交换两个数字

#include <stdio.h>
void Swap(int *p1,int *p2){
    int tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}
int main(){
    int a = 10;
    int b = 20;
    Swap(&a,&b);
    printf("%d %d\n",a,b);
    return 0;
}

指标的级数
Int *p; int **p; int ***p;
理论上无限级,无限套娃,实际上受编译器限制,

指标是一扇门,推开门,后面是整个世界,

C++的参考

参考:reference
已存在变量的别名,

使用参考的必要性:资源使用

思想层:受限制的指标,
语法层:T a; T &p=a;
编译器:给你做了保证,一定是经过初始化的指标
汇编层:和指标一样,

在汇编层,指标和参考是完全一样的,
参考是一个语法糖,T a; T &p=a; 等价于 T *const p = &a

    int x=0;
00676664  mov         dword ptr [x],0  
    int &a = x;
0067666B  lea         eax,[x]  
0067666E  mov         dword ptr [a],eax  
    a = 1;
00676671  mov         eax,dword ptr [a]  
00676674  mov         dword ptr [eax],1  
    int *p = &x;
0067667A  lea         eax,[x]  
0067667D  mov         dword ptr [p],eax  
    *p = 2;
00676680  mov         eax,dword ptr [p]  
00676683  mov         dword ptr [eax],2  

    int *const p2 = &x;
00676689  lea         eax,[x]  
0067668C  mov         dword ptr [p2],eax  
    *p2 = 3;
0067668F  mov         eax,dword ptr [p2]  
00676692  mov         dword ptr [eax],3 

参考的情况:

int a = 1;
const int b = 1;
int &ref1 = a;
int &ref2 = 1;//ERROR
const int &ref3 = b;
const int &ref4 = 1;

Q:唯独int &ref2 = 1;//ERROR?
A:C++的早期这种语法是被允许的,但是在函式呼叫传自变量时,会给程序员带来误解,于是后面就禁止了这种语法,

参考规则的特例:const参考

void f(int &i){}
void f(const int &i){}

int main(){
    int i = 1;
    f(i);//call f(int &i)
    f(2);//call f(const int &i)
    return 0;
}
void f(int &i){}
//void f(const int &i){}

int main(){
    int i = 1;
    f(i);//call f(int &i)
    f(2);//ERROR
    return 0;
}
//void f(int &i){}
void f(const int &i){}

int main(){
    int i = 1;
    f(i);//call f(const int &i)
    f(2);//call f(const int &i)
    return 0;
}

C++语言中就有了新的自变量传递方式:参考传递 void f(T &p) ,实质也是传值,

自定义型别最好用参考传递,可以避免不必要的建构式和解构式的呼叫,
内置型别建议用值传递,自定义型别建议用参考传递,内置型别,值传递会比按参考传递更高效,

解释见:这里

利用参考交换两个数字

#include <iostream>
#include <stdlib.h>
using namespace std;
void swap(int &a, int &b){
    int tmp = a;
    a = b;
    b = tmp;
}
int main(){
    int a = 3;
    int b = 4;
    swap(a, b);
    cout << "a=" << a<<" " << "b=" << b << endl;
    return 0;
}

参考的级数
只能一级,参考的物件必须是一个已存在的地址,参考变量本身的地址,外界不能访问,
References are not objects; they do not necessarily occupy storage,
Because references are not objects, there are no arrays of references, no pointers to references, and no references to references,
int& a[3]; // ERROR
int&* p; // ERROR
int& &r; // ERROR

参考和指标叠加
int a; int *p = &a; int *&r = p; //OK

使用参考的场景:

  • 给函式传递可变自变量
  • 给函式传递大型物件
  • 参考函式回传值;

Q:参考能实作的基本上指标都可以实作,那为什么C++还需要引入参考呢?
A:最初主要是为了支持运算子多载,
c = a + b是可以接受的写法,而c = &a + &b 就不是很方便而且有歧义了,
写法上的方便是要第一考虑的,

Q:C++引入了参考,那为什么C++不和Java一样让指标对使用者不可见呢?
A:历史原因,为了兼容C语言,程序员是自由的,

Q:C++为什么选择&作为参考的识别符号?
A:需要用一个符号告诉编译器,传的是参考,&在C语言中是取地址的作用,于是就选择了它,

Q:this为什么是指标型别,而不是参考型别?
A:历史原因,this诞生早于参考,某种意义上来讲,this应该被设计为参考型别,

Q:Why is "this" not a reference?
A:Because "this" was introduced into C++ (really into C with Classes) before references were added. Also, I chose "this" to follow Simula usage, rather than the (later) Smalltalk use of "self".

Q:拷贝建构式自变量一定是参考,不然编译通不过,为什么?
A:因为在入参的程序中,如果不是参考,会首先进行一次值拷贝;而要实作的就是拷贝构造,就会造成不断的递回最后爆炸,

Q:参考是受限制的指标,哪里受限制了?
A:参考变量本身的地址外界不可获得,当然编译器是可以的,

Q:参考变量是否占有存储器空间?
A:参考可以不占用,也可以占有,语法规定对参考变量的操作都是对被参考物件的操作,

struct S {
    int a;
    int &b;
};
int x = 123;
S s(123,x);

sizeof(S)=?//32位环境等于8

non-const参考的汇编视角
image

const参考的汇编视角
image

说明:const参考变量系结没有地址的物件时,会生成一个临时变量/匿名物件来中转,

全域定义
const int& a = 123; 123的匿名物件在堆上

区域定义
void f{
    const int& a = 456; 456的匿名物件在堆栈上
}

往下走用*,往上走用&,

C++的第一个坑:兼容了C语言的指标,

C++的构造

3种构造语意:

  1. 建构式constructor
  2. 拷贝构造copy constructor
  3. 拷贝赋值运算子copy assignment operator

建构式S()
出厂设定
拷贝构造S(const S &other)
把A的资料复制给B,B(A);
拷贝赋值运算子S& operator=(const S &other)
先把B的资源释放,再把A的资料复制给B,B=A;

变量按存储器分为两种:

  1. 不包含指标,trivial type,篮球,
  2. 包含指标,handle type,风筝和风筝线,

拷贝的分类

  • 浅拷贝
    参考语意(reference semantics)
    缺陷:若写法不对,可能会发生double free,
    Q:为什么编译器所有的默认的行为都是浅拷贝?
    A:深拷贝不一定能实作,指向的物件可能是多型的,也可能是阵列,也可能有回圈参考,所以只能留待成员变量的类来决定怎样实作复制,
    有时候为了防止默认拷贝发生,可以宣告一个私有的拷贝建构式,这种做法比较常见,但不可取,
  • 深拷贝
    值语意(value semantics)
    缺陷:出现了额外的构造和析构,性能损失,
    深拷贝和浅拷贝的本质区别就是两个物件的行为属性是否是独立变化的,

C++的第二个坑:拷贝建构式

思考:
T为handle type,T A(...),T B(A),A赋值给B,如果A不再使用了,能不能让B直接接管A的所有资源呢?(移动语意move semantics)
在不破坏现有语法规则情况下,你会如何设计?

  1. C++03现有语法不支持移动语意,需要新增移动语意,
  2. 如何标识物件的资源是可以被移动的呢?
  3. 这种机制必须以一种最低开销的方式实作,并且对所有的类都有效,
  4. 设计在编译层,与运行层面无关,

C++的设计者们注意到,大多数情况下,右值所包含的物件都是可以安全的被移动的,

左值与右值

左值和右值的概念
CPL语言引入了表达式值的型别value categories这种概念:左值和右值,left or right of assignment,
C语言沿用了类似的分类:左值和其他,locator value and other
C++98 沿用了C语言的分类,但是略微调整,引入了新定义:右值rvalue = https://www.cnblogs.com/txtp/p/non-lvalue,
C++11 新增了xvalue(an “eXpiring” value),并调整了之前左值和右值的定义,
image

(i)has identity: it's possible to determine whether the expression refers to the same entity as another expression, such as by comparing addresses of the objects or the functions they identify (obtained directly or indirectly);
(m)can be moved from: move constructor, move assignment operator, or another function overload that implements move semantics can bind to the expression.
准则 1:能不能分辨两个表达式指的是同一个物体,比如我们可以通过比较地址,
准则 2:能不能使用移动语意,比如看看能不能用呼叫移动建构式,

  • i&~m:lvalue 左值
  • i&m:xvalue 将亡值
  • ~i&m:prvalue 纯右值
  • i:glvalue泛左值
  • m:rvalue右值

C++17
分类和 C++11 是一样的,但是语意上更加明确了,

  • glvalues:有自己地址的长寿物件
  • prvalues:为了初始化而用的短命物件
  • xvalue:资源已经不需要了,而且可以再利用的长寿物件

为了优化这样一个情况:T(T(T(x)))==>T(x),将prvalues的定义略微调整了下,
具体可以参考Copy elision (复制消除)

左值参考和右值参考

T &Lref; // 左值参考,就是传统的c++参考
T &&Rref; // 右值参考
Q:为什么使用&&做为右值参考的识别符号?
A:惯性设计,标准委员玩标点符号是真的可以,

规则:

  • non-const左值参考只能系结non-const左值
  • non-const右值参考只能系结non-const右值
  • const左值参考,可以系结任意,
  • const右值参考,可以系结non-const右值和const右值,注:这个使用的场景很少很少,

如何判定

namespace test {
    template <typename T> struct is_lvalue_reference {
        const static bool value = https://www.cnblogs.com/txtp/p/false;
    };
    template  struct is_lvalue_reference {
        const static bool value = true;
    };
    template  struct is_rvalue_reference {
        const static bool value = false;
    };
    template  struct is_rvalue_reference {
        const static bool value = true;
    };

    template  struct is_lvalue {
        const static bool value = is_lvalue_reference::value && (!is_rvalue_reference::value);
    };

    template  struct is_xvalue {
        const static bool value = (!is_lvalue_reference::value) && is_rvalue_reference::value;
    };

    template  struct is_prvalue {
        const static bool value = (!is_lvalue_reference::value && !is_rvalue_reference::value);
    };

    template  struct is_rvalue {
        const static bool value = (is_xvalue::value || is_prvalue::value);
    };

    template  struct is_glvalue {
        const static bool value = (is_xvalue::value || is_lvalue::value);
    };
}
struct Foo {};
Foo funRetFoo();
Foo &funRetFooLRef();
Foo &&funRetFooRRef();

TEST(TypeTraits, isRvalue) {
    //base type
    EXPECT_FALSE(::test::is_lvalue_reference<int>::value);
    EXPECT_FALSE(::test::is_rvalue_reference<int>::value);
    EXPECT_FALSE(::test::is_lvalue<int>::value);
    EXPECT_FALSE(::test::is_xvalue<int>::value);
    EXPECT_TRUE(::test::is_prvalue<int>::value);
    EXPECT_FALSE(::test::is_glvalue<int>::value);
    EXPECT_TRUE(::test::is_rvalue<int>::value);

    // return obj
    EXPECT_FALSE(::test::is_lvalue_reference<decltype(funRetFoo())>::value);
    EXPECT_FALSE(::test::is_rvalue_reference<decltype(funRetFoo())>::value);
    EXPECT_FALSE(::test::is_lvalue<decltype(funRetFoo())>::value);
    EXPECT_FALSE(::test::is_xvalue<decltype(funRetFoo())>::value);
    EXPECT_TRUE(::test::is_prvalue<decltype(funRetFoo())>::value);
    EXPECT_FALSE(::test::is_glvalue<decltype(funRetFoo())>::value);
    EXPECT_TRUE(::test::is_rvalue<decltype(funRetFoo())>::value);

    // return ref obj
    EXPECT_TRUE(::test::is_lvalue_reference<decltype(funRetFooLRef())>::value);
    EXPECT_FALSE(::test::is_rvalue_reference<decltype(funRetFooLRef())>::value);
    EXPECT_TRUE(::test::is_lvalue<decltype(funRetFooLRef())>::value);
    EXPECT_FALSE(::test::is_xvalue<decltype(funRetFooLRef())>::value);
    EXPECT_FALSE(::test::is_prvalue<decltype(funRetFooLRef())>::value);
    EXPECT_TRUE(::test::is_glvalue<decltype(funRetFooLRef())>::value);
    EXPECT_FALSE(::test::is_rvalue<decltype(funRetFooLRef())>::value);

    // return rref obj
    EXPECT_FALSE(::test::is_lvalue_reference<decltype(funRetFooRRef())>::value);
    EXPECT_TRUE(::test::is_rvalue_reference<decltype(funRetFooRRef())>::value);
    EXPECT_FALSE(::test::is_lvalue<decltype(funRetFooRRef())>::value);
    EXPECT_TRUE(::test::is_xvalue<decltype(funRetFooRRef())>::value);
    EXPECT_FALSE(::test::is_prvalue<decltype(funRetFooRRef())>::value);
    EXPECT_TRUE(::test::is_glvalue<decltype(funRetFooRRef())>::value);
    EXPECT_TRUE(::test::is_rvalue<decltype(funRetFooRRef())>::value);

    int lvalue;
    // 模拟=号左边
    EXPECT_TRUE(::test::is_lvalue_reference<decltype(*&lvalue)>::value);
    EXPECT_FALSE(::test::is_rvalue_reference<decltype(*&lvalue)>::value);
    EXPECT_TRUE(::test::is_lvalue<decltype(*&lvalue)>::value);
    EXPECT_FALSE(::test::is_xvalue<decltype(*&lvalue)>::value);
    EXPECT_FALSE(::test::is_prvalue<decltype(*&lvalue)>::value);
    EXPECT_TRUE(::test::is_glvalue<decltype(*&lvalue)>::value);
    EXPECT_FALSE(::test::is_rvalue<decltype(*&lvalue)>::value);

    //operator++()
    EXPECT_FALSE(::test::is_lvalue_reference<decltype(lvalue++)>::value);
    EXPECT_FALSE(::test::is_rvalue_reference<decltype(lvalue++)>::value);
    EXPECT_FALSE(::test::is_lvalue<decltype(lvalue++)>::value);
    EXPECT_FALSE(::test::is_xvalue<decltype(lvalue++)>::value);
    EXPECT_TRUE(::test::is_prvalue<decltype(lvalue++)>::value);
    EXPECT_FALSE(::test::is_glvalue<decltype(lvalue++)>::value);
    EXPECT_TRUE(::test::is_rvalue<decltype(lvalue++)>::value);

    //operator++(int)
    EXPECT_TRUE(::test::is_lvalue_reference<decltype(++lvalue)>::value);
    EXPECT_FALSE(::test::is_rvalue_reference<decltype(++lvalue)>::value);
    EXPECT_TRUE(::test::is_lvalue<decltype(++lvalue)>::value);
    EXPECT_FALSE(::test::is_xvalue<decltype(++lvalue)>::value);
    EXPECT_FALSE(::test::is_prvalue<decltype(++lvalue)>::value);
    EXPECT_TRUE(::test::is_glvalue<decltype(++lvalue)>::value);
    EXPECT_FALSE(::test::is_rvalue<decltype(++lvalue)>::value);
}

记住一点:左值参考直接作用于lvalue,右值参考直接作用于xvalue,
Q:谁直接作用于prvalue呢?
A:只能间接作用:右值参考、const左值参考,生成一个指标型别的匿名变量做中转,

移动语意

如何让自定义物件支持移动语意?

  • 移动构造move constructorS(S &&other) noexcept
  • 移动赋值运算子move assignment operator S& operator=(S &&other) noexcept

note:需要加noexpect,
目的:告诉编译器:这个移动函式不会抛出例外,可以放心地呼叫,否则,编译器会呼叫拷贝函式,

C++11后,STL都支持了移动语意,移动语意对基本型别没有性能提升的作用,

Q:如何将一个变量转为右值参考?
A:static_cast<T &&>(t)
写法一:

string s = "abcd";
string s1(static_cast<string &&>(s));
s1(s);
string s2 = static_cast<string &&>(s);

这种写法麻烦,如何简写?模板,

写法二:

//VS
template <class _Ty>
struct remove_reference<_Ty&&> {
    using type                 = _Ty;
    using _Const_thru_ref_type = const _Ty&&;
};

template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;

// FUNCTION TEMPLATE move
template <class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
    return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}
string s = "abcd";
string s1(move(s));
string s2 = move(s);

由此诞生了std::move,在头档案中,

利用移动交换两个数字

void swap(T &a1, T &a2){
    T tmp(std::move(a1)); // a1 转为右值,移动建构式呼叫,低成本
    a1 = std::move(a2);   // a2 转为右值,移动赋值函式呼叫,低成本
    a2 = std::move(tmp);  // tmp 转为右值移动给a2
}

Q:move的自变量是&&,为什么传入左值也是可以的?
A:万能参考

A=B B的资源给A了,A的资源自己处理掉的,如果B在外面继续使用,则是未定义行为,

万能参考

universal reference
概念:使用T&&型别的形参既能系结右值,又能系结左值,
T&&在代码里并不总是右值参考,

万能参考一定涉及到型别推导的,没有型别推导就是右值参考,
具体规则:这篇文章
使用场景有2个:

template<typename T>
void f(T&& param); // param is a universal reference

auto&& var2 = var1; // var2 is a universal reference

参考折叠规则

reference-collapsing rules
引入该规则的原因:C++中禁止reference to reference,为了通过编译器检查,
当左值参考和右值参考同时出现在型别定义中时,需要如何处理?
约定:只有&& && = &&,沾上一个&就变左值参考了,

T && && ==>T &&
T && &  ==>T &
T &  && ==>T &
T &  &  ==>T &

Q:为什么要这幺约定?
A:左值参考有副作用

函式内外自变量型别不匹配

template<typename T>
void f(T&& a){
    g(a);  // 这里的 a 是什么型别?
}

// 版本 1
template<typename T>
void g(T &){ cout << "T&" << endl; }

// 版本 2
template<typename T>
void g(T &&){ cout << "T&&" << endl; }

int num;
f(0);
f(num);

输出:
T&
T&

a是变量,是左值,所以输出T&,
但是0是数字,是右值,为什么进去后就成了左值?能不能一致?
Q:一定要一致幺?
A:在某些场景不一致会有问题,需要提供一种保证前后语意一致的机制,

Q:怎么才能实作一致呢?
A:还是static_cast,

template<typename T>
void f(T&& a){
    g(static_cast<T &&>(a));  
    //g(static_cast<T>(a));  这样写也可以
}

// 版本 1
template<typename T>
void g(T &){ cout << "T&" << endl; }

// 版本 2
template<typename T>
void g(T &&){ cout << "T&&" << endl; }

int a;
f(0);
f(a);

输出:
T&&
T&

这种写法麻烦,如何简写?模板,

Forward

转发:某些函式需要将其中一个或多个实参连同型别不变地转发给其他函式

//VS
// FUNCTION TEMPLATE forward
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
    return static_cast<_Ty&&>(_Arg);
}

template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
    static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
    return static_cast<_Ty&&>(_Arg);
}

就可以简写:

template<typename T>
void f(T&& a){
    g(forward<T>(a));  
}

好像也没啥好处,就是static_cast替换为了forward,
区别:

  • static_cast 关键字
  • std::forward 模板函式,支持函式模板的变长自变量,在头档案中,

例子:make_unique

// FUNCTION TEMPLATE make_unique
template <class _Ty, class... _Types, enable_if_t<!is_array_v<_Ty>, int> = 0>
_NODISCARD unique_ptr<_Ty> make_unique(_Types&&... _Args) { // make a unique_ptr
    return unique_ptr<_Ty>(new _Ty(_STD forward<_Types>(_Args)...));
}

template <class _Ty, enable_if_t<is_array_v<_Ty> && extent_v<_Ty> == 0, int> = 0>
_NODISCARD unique_ptr<_Ty> make_unique(size_t _Size) { // make a unique_ptr
    using _Elem = remove_extent_t<_Ty>;
    return unique_ptr<_Ty>(new _Elem[_Size]());
}

Perfect forward

转发时,需要保持被转发实参的所有性质不变:是否const、以及是左值还是右值,
完美转发 = std::forward + 万能参考 + 参考折叠

用途:一般作为多自变量函式呼叫的中间层,

struct A{
    A(int &&n){ cout << "rvalue overload, n=" << n << endl; }
    A(int &n){ cout << "lvalue overload, n=" << n << endl; }
    A(const int &&n){ cout << "rvalue const overload, n=" << n << endl; }
    A(const int &n){ cout << "lvalue const overload, n=" << n << endl; }
};

class B{
public:
    template<typename T1, typename T2, typename T3, typename T4>
    B(T1 &&t1, T2 &&t2, T3 &&t3, T4 &&t4) :
        a1_(std::forward<T1>(t1)),
        a2_(std::forward<T2>(t2)),
        a3_(std::forward<T3>(t3)),
        a4_(std::forward<T4>(t4)) {}
private:
    A a1_, a2_, a3_, a4_;
};

int main(){
    int i = 1, j = 2, k = 3, m = 4;
    int &i_lref = i;
    const int &j_clref = j;
    int &&k_rref = std::move(k);
    const int &&m_crref = std::move(m);

    B b1(1, 2, 3, 4);
    B b2(i, j, k, m);
    B b3(i_lref, j_clref, k_rref, m_crref);
    B b4(i_lref, j_clref, std::move(k), static_cast<const int &&>(m));

    return 0;
}

直观的感受:建构式只用写一个,就可以满足所有情形,代替了4^4=256种多载形式,

perfect的由来:自变量传递的七种方案

不支持转发的场景 EffectiveModernCppChinese/item30.md

汇编视角

第一个视角
image

右值参考就是一个指向一个匿名变量的指标,右值参考就是给了外界接触这个匿名变量的机会,
可见初始化一个右值参考其实是开辟了两块空间,一块是右值参考型别那么大的匿名变量,一块是指向这个匿名变量的指标,

第二个视角
image

修改右值参考值的程序也分为两步,取出指标的值,也就是匿名变量的地址,把右值赋值给地址所指的匿名变量,和修改指标的值一样的,

Q:如何理解“右值参考延长了右值的生命周期”?
A:右值被放到了一个变量里,已经拥有了存储器,当然就避免了被从暂存器里扔出去就消失的命运,这一块存储器的生命周期就和右值参考变量一致了,

最佳实践

网上找的一个图:
image

资源使用语意

老师给大宝一个球,大宝拿去玩,老师说:注意些别玩坏了,

球的状态:可变:non-const,不可变:const,
意图的体现,

大宝在玩球,小宝也想玩,怎么办?
1. 小宝加入他,一起玩,
2. 大宝不给,小宝买了一个一模一样的球,各玩各的,
3. 大宝说:我不玩了,给你玩吧,小宝接着玩,

需要三个语意来表达(实际上也足够了):

1. 别名(二者唯一)link
2. 复制(二者各一)CTRL+C、CTRL+V,
3. 移动(一有一无)CTRL+X、CTRL+V,

别名对应参考,复制对应资源复制,移动对应资源转移,
性能的体现,

C++实作
别名语意:reference
复制语意:copy constructor、copy assignment operator,
移动语意:move constructor、move assignment operator,

底层实作都是依赖指标,

设计原则

C++语言设计规则 摘自《C++语言的设计和演化》
"资源使用"上体现的原则:

  1. 全域平衡
  2. C++ 的发展必须由实际问题推动
  3. 对不用的东西不需要付出任何代价(零开销规则)

简单的东西依然保持简单,

总结

C++的两个坑:1.兼容了C语言的指标 2.拷贝建构式
参考解决了资源使用问题,但无法解决资源管理的问题,

指标是一个潘多拉盒子,要想解决根本问题,只能把盒子抛掉,这个对于C++来说不可能,
但是,C++在如何方便程序员做资源管理这个方向上也做了很多尝试,

开放问题

Q:“指标思想”是所有编程语言的基石,为什么?
A:

参考资料

https://blog.csdn.net/l477918269/article/details/90233908
https://zhuanlan.zhihu.com/p/374392832
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm
https://en.cppreference.com/w/cpp/language/rule_of_three
https://stackoverflow.com/questions/3582001/what-are-the-main-purposes-of-using-stdforward-and-which-problems-it-solves
https://www.zhihu.com/question/363686723/answer/1910830503
https://blog.csdn.net/qq_33113661/article/details/89040579?
https://zhuanlan.zhihu.com/p/265778316
https://stackoverflow.com/questions/3582001/what-are-the-main-purposes-of-using-stdforward-and-which-problems-it-solves
https://www.cnblogs.com/xusd-null/p/3761637.html
https://zhuanlan.zhihu.com/p/265815272

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *