Windows 提示被管理员禁止安装核显驱动、WSL 也无法启动
原因是很早之前按照知乎的说法启用了这个设置,本想禁用系统更新时自动安装旧版本的核显驱动,但也使得我的电脑核显驱动坏掉且不能自行安装。
恢复核显驱动首先应该关闭这项,然后去下载对应驱动安装,或者回退到旧的驱动。
这项配置启用时,我的 WSL 也无法正常进入!
原因是很早之前按照知乎的说法启用了这个设置,本想禁用系统更新时自动安装旧版本的核显驱动,但也使得我的电脑核显驱动坏掉且不能自行安装。
恢复核显驱动首先应该关闭这项,然后去下载对应驱动安装,或者回退到旧的驱动。
这项配置启用时,我的 WSL 也无法正常进入!
设置 apt 代理。编辑 /etc/apt/apt.conf,格式如下(ip 会变):
// 很奇怪,注释竟然是//开头,而不是#
Acquire::http::Proxy "http://192.168.1.56:12366/";
Acquire::https::Proxy "http://192.168.1.56:12366/";
sudo 没有
$http_proxy
代理的原因是默认不传递环境变量,使用-E
选项传递环境变量就可以不设置上面的代理。
清华大学开源软件镜像站 https://mirrors.tuna.tsinghua.edu.cn/help/debian/
先用自己电脑给 apt 开代理,然后改镜像,安装一些 https 源需要的东西,然后 apt update。之后安装 build-essential mlocate 等等。
复制、默认构造都是按需生成的。对于平凡的情况不需要生成,只是在语意上满足“拥有构造函数”的含义。
x86-64 gcc 13.1 -std=c++20
struct Point {
int x;
int y;
Point() = default; // 即便显式声明了默认构造函数,也不会合成
};
int main() {
auto some_point = Point{}; // {}初始化对聚合类有清零的效果
}
结果是会把基类同签名的所有非 final 虚函数都重写了,而且实现方式相同。尽管基类的虚函数签名一样,但是他们没有关联性,所以在子类的虚表中占两个槽(slots)(一个槽是一个指针)。同样的,如果 Interface 中有虚函数 foo,而 A 和 B 都继承了 Interface,C 继承了 A 和 B。如果 A 和 B 没有虚拟继承 Interface,那么在 C 的对象调用函数 foo 时将出现 ambiguous 指代错误。如果 C 重写了 foo 函数,那么指代就还是明确的。或者,如果 A 和 B 都是虚拟继承自 Interface,那么也不会有编译错误。但这样通过指针/引用调用虚函数 foo 就需要先取虚基类子对象 this 的偏移,修改 this 之后再从 vptr 中读虚函数 foo,开销是 4 次访存(将虚拟继承和虚函数调用的代价累加起来了) 。
重写签名相同的虚函数时,生成的函数代码只能对其中一个基类的虚函数做 this 修正。
#include <cstdio>
using namespace std;
struct A {
int x;
virtual void foo() { printf("A::foo() x=%d\n", x); }
};
struct B {
virtual void foo() { printf("B::foo()\n"); }
};
struct C : A, B {
void foo() override { printf("C::foo()\n"); }
};
int main() {
C c;
A *p = &c; // <--
p->foo();
A *q = new A;
q->foo();
B *r = new C; // <--
r->foo();
}
首先 C::foo()
代码只有一份(生成多份太浪费了),通过上面 p
指针调用 foo()
,会从 vtable 中选中 C::foo()
,然后用 C 的 this(这是因为 A 和 C 的 this 是重合的,不需要修改)。如果用 r
指针调用foo()
,则需要将 B 的 this 修改成 C 的 this,那么直接复用 C::foo()
代码就不行了!
要注意空指针是特殊情况:
这个小节没有提到 vptr 的处理。
写复制构造函数和复制赋值操作符的时候要记得把基类和成员都正确初始化(用默认的最好)。复制构造函数也算是构造函数,所以要使用成员初始化列表来正确初始化。
复制赋值操作符对基类赋值需要先将 *this
转换成基类的引用,然后用赋值表达式调用基类的赋值操作符。毕竟赋值操作符也不是构造函数,构造过程需要手写的部分有很多还容易写错!这也是为什么复制构造临时对象 + swap 的方法很受欢迎。
#include <cstdio>
struct A {
int a;
A() = default;
A(A const &) { puts("A(const A &)"); }
A &operator=(A const &) {
puts("A &operator=(A const &)");
return *this;
}
};
struct B {
int b;
B() = default;
B(B const &) { puts("B(const B &)"); }
B &operator=(B const &) {
puts("B &operator=(B const &)");
return *this;
}
};
struct D {
int d;
D() = default;
D(D const &) { puts("D(const D &)"); }
D &operator=(D const &) {
puts("D &operator=(D const &)");
return *this;
}
};
struct C : A, B {
int c;
D d;
C() = default;
// 错误写法:(没有通过初始化列表对基类和成员起到复制作用,它们被默认构造)
// C(const C &) {
// }
// 等效于默认的复制构造函数
C(const C &other) : A(other), B(other), d{other.d} {}
// 等效于默认的赋值操作符
// 很麻烦,不如先复制构造再swap
C &operator=(C const &other) {
if (this == &other) {
return *this;
}
// 右边的 other 会隐式转换成对应类型
static_cast<A &>(*this) = other;
static_cast<B &>(*this) = other;
d = other.d;
return *this;
}
};
int main() {
C c;
puts("copy construction");
C c1 = c;
puts("copy assignment");
c1 = c;
}
初始化会按照变量声明的顺序进行。因此虽然下面的代码想要用 j 的新值初始化 i,但实际上是 i(j)
先被执行,然后才是 j(val)
。
不过,构造函数代码块中的初始化过程始终发生于成员初始化列表之后。