总结一下以及可以用来做面试题的题目。最后总结以下所有的可以出题用于复习和面试出题。
内容来源:
The content below is a collection based on the source pages licensed in GNUFDL, All contents collected from the origin are redistributed in GNUFDL:
GNU Free Documentation License
C++ Idioms 条目分析:
- Acyclic visitor 没搞明白
- address of 是为了解决重载 operator & 的问题。
- Attach by initializaiton,对于 event-loop 的程序以及接管 main 的程序(通常是 GUI 或者像 gtest 这种),有一些 main 之前的逻辑 。实现方案是用 static storage,如果有多个翻译单元的顺序要求,通过写 meyers singleton 然后写一个初始化函数依次调用他们。
如果我需要在程序执行之前初始化一系列的对象,怎么初始化? |
- 律师 Attorney-client,因为 friend 可以访问所有 private 成员,解决方案是加一个中间人--律师
如何控制 friend 类能够看到的成员?/如何对 friend 的视图做权限限制? |
代码示例:
class Client { private: void A(int a); void C(double c); friend class Attorney; }; class Attorney { private: static void callA(Client & c, int a) { c.A(a); } friend class Bar; }; class Bar { // Bar now has access to only Client::A and Client::B through the Attorney. }; |
|
- Barton-Nackman trick,就是说,模板类写 friend 的 operator 比较,而不是写在 scope 外面。这样比较函数就不是模板重新另外决议的函数了,而是实例化之后的函数。一种 CRTP 模式 wrapper。
operator 函数重载的时候用成员函数、friend 和在 namespace 中写有什么区别和优缺点? |
代码示例:
template<typename T> class EqualityComparable { public: friend bool operator==(const T & lft, const T & rgt) { return lft.equalTo(rgt); } friend bool operator!=(const T & lft, const T & rgt) { return !lft.equalTo(rgt); } }; class ValueType : private EqualityComparable<ValueType> { public: bool equalTo(const ValueType & other) const; }; |
- Base-from-Member,如果需要在 base 构造之前使用 derived-class 的 member 的时候,一般的做法是通过 `nullptr` 和额外的一个 init 函数来实现。这里讲解了一个通过依赖于类的构造顺序,从而成功的把 has-a 变成了加一个 middleware 的 is-a。
基类需要用到派生类的成员,怎么把派生类的成员传过去?/如何实现先初始化类的成员再初始化基类? |
(其实这个是一个设计问题,尽量不要这样设计吧!但是有时候确实希望实现,比如非侵入式的修改,然后你又想用 has-a + inheritance 来封装一个类,比如下面的这个 fdostream。boost utility 还专门做了一个模板类)。 |
代码示例:
class fdoutbuf : public std::streambuf { public: explicit fdoutbuf(int fd); //... }; struct fdostream_pbase // A newly introduced class.{ fdoutbuf sbuffer; // The member moved 'up' the hierarchy. explicit fdostream_pbase(int fd) : sbuffer(fd) {} }; class fdostream : protected fdostream_pbase // This class will be initialized before the next one. , public std::ostream { public: explicit fdostream(int fd) : fdostream_pbase(fd), // Initialize the newly added base before std::ostream. std::ostream(&sbuffer) // Now safe to pass the pointer. {} //... }; |
- Boost mutant=> BiMap 节约美德,对于一个 pair,如何避免复制的情况下实现 first 访问 second,second 访问 first 呢?简单,只需要进行一个 adapter 模式和暴力 POD `reinterpret_cast` 和引用/指针就行了。`boost::bimap` 的好处在于,避免复制的时候可以正向和反向同时查询,比如 hostname <-> ip address。
如果我需要查询大量 key 和 value 的相互查询,怎么优化? |
这种实现完全是编译开销,因为解释 first 和 second 只是不同的汇编解释。 |
代码示例:
template <class Pair> struct Reverse { typedef typename Pair::first_type second_type; typedef typename Pair::second_type first_type; second_type second; first_type first; }; template <class Pair> Reverse<Pair> & mutate(Pair & p) { return reinterpret_cast<Reverse<Pair> &>(p); } |
- Calling Virtuals During Initialization,首先复习几个问题,vptr 的填充是在 base ctor 之后, member init 之前进行的 [class.base.init]。因此构造函数中禁止了动态查找和虚函数绑定,此时只会调用自己的虚函数。解决方案即 two phase initialization ,一般结合 factory pattern 来做,不然 RAII 就无了,还是手写各种 init。法二是用 CRTP 不用虚函数,然后加限定域查找,但是太丑了而且 GP constraint 难以维护。
你知道 vptr 是什么时候填充的吗?(先问: vtable 是什么时候初始化的? |
基类调用虚函数的时候调用的是谁的实现?如果在构造函数调用呢?如果在析构函数中调用呢?(这不是 UB) |
构造函数中可以对外暴露 this 指针吗?为什么不能。(废话) |
- Construction tracker,利用逗号表达式(注意C++20 中下标内逗号不适用)实现构造成员捕获异常的 track。具体来说,就是构造的时候通过 `(tracker = TAG, (args...))` 作为参数去构造成员数据类型,从而可以知道是谁抛出的。或者另外起多个 bool 变量。
构造的时候怎么知道构造哪个成员对象时抛出了异常? |
补充一个我从来没用过的东西先:
C++ 中构造 try catch block |
class A{ ... public: A() try: m1(), m2()...{} catch(...){} }; |
从而可以设计这样的代码:
... A( TrackerType tracker = NONE) try // A constructor try block. : b_((tracker = ONE, "hello")) // Can throw std::exception , c_((tracker = TWO, "world")) // Can throw std::exception { assert(tracker == TWO); // ... constructor body ... } ... |
- Copy-on-write,这个 陈硕 在 muduo 书里面写过,Meyer 也提过,曾经的 gcc string 还用过。基本原理是用 const 访问的时候,就用原来的 shared_ptr, 如果需要用非 const 利用的时候,比如解引用或者调用了某些 setter ,利用 `shared_ptr` 的 `unique()` 接口判断(这个东西是不是安全的知乎还有一个问题讨论,SO 也有....)要不要复制一份。
有一个很大的对象可能需要多个线程用到,大部分是读,少部分需要修改,怎么优化? |
示例代码:
template <class T> class CowPtr { public: typedef std::shared_ptr<T> RefPtr; private: RefPtr m_sp; void detach() { T* tmp = m_sp.get(); if( !( tmp == 0 || m_sp.unique() ) ) { m_sp = RefPtr( new T( *tmp ) ); } } public: ... impl all copy/move stuffs... }; |
- EBO:不说了,太简单了,对于可能的空类(最常见的是 Functor,用他基本是为了用他的 `operator()`),通过继承实现组合。一般 wrap 一个中间层,比如 stl 的 vector、map 等非 pmr allocator 的就是这样做的。但是 EBO 了之后,不同类的地址就可以一样,标准也没有保证多继承的时候地址可以区分。
空类的大小是多少?如果要用到很多空类,怎么优化? |
- Erase-Remove:Effective STL I32 不知道这个为什么算一个 idiom... 大概是说,`std::remove` 只是 move,并不是 remove。因为他是通用的,他没有用 container 的引用参数,因此没办法修改 meta。所以一般要结合 `containter.erase` 来使用...
怎么从容器类里面删除元素?(std::remove 怎么实现的?)/这个根本不算一个能问的问题... |
- Execute-Around Pointer: 通过指针和 `operator->`, `operator *` 的 overloading 封装一个 indirection 加载在对象访问的切面上,构造一个临时对象,因此也支持了 RAII。而且结合 cdr 和 con 模式,可以形成一个链条(继承)。
AOP, Aspect Oriented Programming/面向切面编程 |
就是给所有的操作前后(运行步骤=切面)添加一些额外的重复脏活。Spring AOP 就是用这个,比如鉴权的时候。稍底层一点的用途比如每个点加 lockguard 和不加等。实际等于运行的时候调用的是代理的(Proxy),从而实现 python decorator 的效果。shared_ptr、unique_ptr 也是一种 AOP。 |
下面代码中,Aspect 做的只是封装了指针数据和 operator overloadings 而已:
template <class NextAspect, class Para> struct Logging : Aspect<NextAspect, Para> { public: Logging (Para p) : Aspect <NextAspect, Para> (p) { std::cout << "Before Log aspect" << std::endl; } ~Logging () { std::cout << "After Log aspect" << std::endl; } }; |
如果我要在每次对象的成员函数调用之间插入一些代码,怎么实现? |
如果我在每个函数返回的地方要加一些代码,怎么实现? |
用 RAII。感觉这个点也没有什么问题好问的。我之前写过一个重载 | 运算符就是用的 AOP。 |
- Expression-Template:通过模板、Functor 实现表达式类,用于支持动态组装表达式, DSL、lazy evaluation。编译 parsing 中常用。
代码示例:
template < class L, class H, class OP > struct DBinaryExpression { L l_; H h_; DBinaryExpression (L l, H h) : l_ (l), h_ (h) {} double operator () (double d) { return OP::apply (l_ (d), h_(d)); } }; struct Add { static double apply (double l, double h) { return l + h; } }; |
怎么实现 lazy evaluation? |
闭包、functor、函数指针 |
如果我要运行时决定运算的表达式,运行时修改程序代码,怎么做? |
写一个 jvm,写一个 lua vm .... 写 AST、用这个 expression-template |
- Inline Guard Macro: 这是其中一个原因为什么一些源码里面的关键字不是直接用的,而是用用宏加的,还有一些变量定义、返回值和返回等都是用宏包的一个,基本就是为了编译器的兼容和方便控制 release 和非 release 版本。
ipp 文件的用途 |
一般 ipp 文件是基于头文件或者实现 include 再抽一部分出来,boost 里面有大量的 ipp 文件,主要是用来抽开 implementation 和干净的头文件以及用于链接编译的非 inline 的 impl。 |
这里主要讲解 inline 的既能避免不 inline 的 redefinition,又能避免 inline 了必须放头文件的用法:
// test.ipp file INLINE void Test::func(){} /*******************************************************************/ // test.hpp file#define MYPROJECT_TEST_H class Test { public: void func();}; #ifdef MYPROJECT_INLINE_ENABLED #define INLINE inline #include "test.ipp" #endif /*******************************************************************/ //test.cpp file #include "test.hpp" #ifndef MYPROJECT_INLINE_ENABLED #define INLINE #include "test.ipp" #endif |
- Inner Class: 对于 string 这种不能继承的类,可以通过继承变成组合 + static cast operator 来实现,当然实际表达能力肯定是比直接继承或者 go 的 embedded 差一点的。
不放代码了,太长了....
- Int-To-Type: 就是 tmp 里面把同一个类型的数值变成不同类型的...比如 integral_constant 。这个不具体说了。
模板编程里面怎么让 1 和 2 变成不同的类型? |
- Interface: 纯虚函数有意义吗?我认为 interface 的最佳实现应该是 GP + constraint(concept),比如 golang 里面的那个 interface (的写法),但是 go 的实现是用 hashtable 来做的虚表我不理解...不过他也没有静态模板,要运行时决定使用虚表也理所当然了(但是这个虚表好像是运行时造的,因为他并没有指定一个 `implements` 语法, 因此必须根据反射信息来建立表或者查询)。相似的还有 Rust 中的 trait 和 fat pointer (效率比原 pointer 高是因为 fat pointer 等于一层值语义的 wapper 实现的指针语义,不用再走一趟才知道 vtable 的地址,而且对象数据存储也不用偷偷存一个和行为有关的数据了)。类似 rust 的两个指针的实现提案 p0957 从语法层面修订到现在的库层面(revision 7)搞了一个 `std::proxy` 的 wrapper,可能有望 C++26 进入 std。p0957 的思想等于是不把 `多态` 这一语义绑定到类本身,因为多态某种程度上可以认为是用法上的。具体这里不再深究。
Polymorphism is not a property of the type, but rather a property of how it is used. |
Inheritance Is The Base Class of Evil (Adobe Sean Parent 2003) Better Code: Runtime Polymorphism - Sean Parent (still Adobe Sean Parent NDC 2017) |
- making-new-friend: 在类里面定义 friend 函数,避免特化影响决议... 因为 friend 函数实际是 static 的。或者说,就是 friend 函数不应该是一个模板,而是一个实例化出来的有特定类型信息的函数。
代码示例:
template<typename T> class Foo { T value; public: Foo(const T& t) { value = t; } friend ostream& operator<<(ostream&, const Foo<T>&); }; template<typename T> ostream& operator<<(ostream& os, const Foo<T>& b) { return os << b.value; } |
上面这种写法无法编译,因为这个函数不是一个模板函数,除非 friend 中加上 <> 限定。因此不如直接 inner 定义。不过如果内部定义的话不加限定只能 ADL 不能参与非 ADL: [namespace.memdef]/3。
ADL: argument dependent lookup |
意义是如果参数已经加了限定符,函数调用就不用限定符,这样对于 operator 来说是很重要的,不如代码就会很丑。ADL 的优先级超越 using,例如以下的例子: |
using std::swap; |
这里 `obj1` , `obj2` 可以是通过 `A::obj1` 这种方式引入的。如果非模板冲突的时候(能编译的时候说明没有实例化模板,不能编译说明存在重载),还会引发 ambiguous。 |
如果没有 ADL,每次调用都需要加限定符 `X::`。 |
具体到这里,friend 如果定义在内部,而且参数和类型无关,则无法调用。比如以下的代码:
struct S { friend void f() {} friend void g(S const&) {} } const s; int main() { // f(); // error: 'f' was not declared in this scope // S::f(); // error: 'f' is not a member of 'S' g(s); // S::g(s); // error: 'g' is not a member of 'S' } |
friend 函数是不是破坏了封装吗? |
要看你怎么看待封装,如果把封装的谈论对象限定在单个类里面,是的。但是 OOP 不等于面向单一类编程。另外一个层面是 C++ 不是纯粹的 OO 语言(BS 说封装都不是 C++ 的属于),另外,Java 中的 get set 本身也是一个曲线救国,这个角度来看 friend 比 get set 更加的安全。 |
- Multi-statement Macro:为了让 macro 后面可以且必须加分号,通过一个 `else` 或者 `while(0)`来实现。
- Member Detector: compile time reflection 和 type_trait 但是还是要很麻烦的。这里通过 SFINAE 实现。基本原理就是让他的成员函数有多个模板重载,然后如果有这个成员,就会重载成功。具体的不说了,巧妙利用 decl type 和修改参数类型就行了。
- Named Constructor:通过 `static return {}` 来构造对象,提高可读性。就像 make_shared 这些。
- Named loop:用 macro magic 来实现 `break(tag)` 的语法。
直接看代码把家人们:
#define named(blockname) goto blockname; \ blockname##_skip: if (0) \ blockname: #define break(blockname) goto blockname##_skip int main(void) { named (outer) for (int i = 0; i < 10; i++) { int j = 0; named(inner) for (; j < 5; j++) { if (j == 1) break(outer); if (j == 3) break(inner); } std::cout << "after inner\n"; } return 0; } |
原理就是这样的原理... 不说了。Java 和 go 都支持 label break 了。
SO 上面有一个更安全写法的讨论:
- Named Parameter:为了让默认参数不局限于吊车尾,通过一种 placeholder + setter 来做变量的初始化,前提是所有的 member 都有 default,没有的话就丢到真的构造里面,有 default 的通过 setter 来搞。看代码吧家人们。boost 里面的图论算法用到了。另外对于参数太多的函数(超绝参数化了属于是,这种情况如果走构造函数一般要上栈传参了)也能用上。
代码示例:
int main (void) { // The following code uses the named parameter idiom. std::cout << X::create().setA(10).setB('Z') << std::endl; } |
- Nifty counter, 这个东西是用来保证用到了才初始化 singleton 的(本质上等于 singleton 模式)。这里有挺多点需要掌握的。下面一个一个来分析。
dynamic library 中的变量 |
shared 库的所有变量对于多个程序而已都是独立的,因为他们的地址空间是独立的,shared library 只会 share 一个东西,就是代码段。因此我们的讨论主要是讨论 translation unit 的独立。 |
C++ 中的 static |
|
static 变量的构造时机 |
(1): The correct state should be: "before any function from the same translation unit is called".
|
如果对 meyer's singleton 进行 cache 的操作,会引发问题,因为等于你还是要做一套判断。因此最好不要做全局 cache,可以在用到他的对象身上加一个 reference cache。比如下面的情况中,由于 cache 分散在不同的编译单元:
// user2.cpp -> libu2.so / u2.o #include "shared_singleton.hpp" my_class &cache2 = singleton<my_class>::Instance(); // user1.cpp -> libu1.so / u1.o #include "shared_singleton.hpp" my_class &cache1 = singleton<my_class>::Instance(); // main.cpp -> a.out extern my_class cache1; extern my_class cache2; int main() { using namespace std; cout << "cache1 addr: " << &cache1 << endl; cout << "cache2 addr: " << &cache2 << endl; } |
此时由于 extern 进来的两个变量实际来自两个翻译单元,无论他们以静态链接还是动态链接,他们都是两个独立的 singleton。下面加一部分怎么确认 local static singleton 是独立的符号的点,通过大纲视图折叠。
因为编译器保证了两个引用变量在程序执行之前就初始化。上面的文件实现,我们可以通过以下命令查看符号属性(nm 命令是解析 object 文件的):
nm -a libsu1.so | grep t | xargs c++filt | grep -5 Instance |
我们可以看到:
W singleton<my_class>::Instance() 0000000000004028 u singleton<my_class>::Instance()::t |
u 表示这是一个 unique 符号。
单例模式在不同翻译单元中是同一个吗? |
全局变量什么时候初始化?全局变量的初始化顺序是怎么样的?析构呢? |
同一个翻译单元中,按照定义的顺序在任何函数执行之前初始化。不同翻译单元不知。析构顺序和初始化顺序相反。对于编译期可以知道的变量(一般是字面常量、简单的函数初始化等),一般会在 load 的时候,执行之前就执行完毕。 |
如果我需要在程序执行之前执行一段程序,怎么实现? |
通过全局变量的声明、定义来实现,即 RAII 利用构造函数实现或者利用变量初始化 lambda。 |
- NVI-Non-virtual-Interface/Protected Virtual:Herb Sutter 也推动 p0957,持续搞模板,看来他也不喜欢继承。类似子类沙箱模式,方便注入 decorator 逻辑。本质上等于 framework 完形填空。当然问题也很明显就是每一个成员都要写两次。但是这其实也不是很糟糕的事情如果对象参数和成员方法太多,我们总是可以进行分离的,至少 Single responsibility 还是可以用一用的。
- Object Generator :即各种 make_xx, bind_xx。和工厂方法的区别就是,工厂是用来实现多态的,object generator 是用来减少编程负担的(比如十几行的 type trait 加 typedef...)...
- Parameterized base class: 这个和 CRTP 的不同是 CRTP 是让父类获取子类信息,PBC 是用继承来实现组合的语义,或者说模块化。
- Policy Clone/policy rebind:Allocator 的 rebind 实现思路。这是因为 C++ 中,`Policy<_Th1>` 可以匹配模板 `template <typename T>`中的 `T`,因为他本身就是一个类型,除非我们用 `template<typename <class _Th>P>` 去匹配,这样可以匹配到 `P = Policy && _Th = _Th1` 但是这样写的话灵活性不好。因此一般通过让 Policy 自己支持 rebind。具体代码也不放了。我认为这个绑定 allocator 和分配类型的设计就十分混乱...
STL 的 allocator 是怎么让不同类型的也能分配内存的?比如我给 map<int> 传一个 std::allocator<int>, 分配节点的内存的时候怎么能通过 int allocator 分配内存呢? |
STL 中这一点很奇怪,因为他自己都会用 rebind,为什么不能对用户的参数直接做 rebind 呢?可能大概是为了零开销以及模板的功能性的历史原因。节点的 rebind 是必要的,而用户传进来如果真的需要,用户应该自己 rebind。 |
- Requiring or Prohibiting Heap-based Objects:通过一些手段禁止堆分配以及只允许堆分配。最简单的方案是,protect 析构函数->只允许堆分配,protect static 的 operator new 的 override -> 只允许栈分配或者类自己管理的堆分配。
怎么限定一个类只能堆上创建?怎么限定一个类只能栈上创建? |
- Return Type Resolver:利用类的构造函数伪造成函数,结合 operator T (static_cast)来实现函数返回值的推导,从而生成他们要的东西。
直接看代码吧家人们:
class getRandomN { size_t count; public: getRandomN(int n = 1) : count(n) {} template <class Container> operator Container () { Container c; for(size_t i = 0;i < count; ++i) c.insert(c.end(), rand()); // push_back is not supported by all standard containers. return c; } }; int main() { std::set<int> random_s = getRandomN(10); std::vector<int> random_v = getRandomN(10); std::list<int> random_l = getRandomN(10); } |
type a = f(), 怎么让 f 支持不同的 type ?(这个不能算一个问题吧...) |
- Safe bool: 写 operator bool 的时候一定要加 explicit (除非你知道你在做什么),否则会引发问题:if(a==b) 其中 a b 对象只支持了 operator bool() 因此,很可能得到错误的结果。
让对象支持 bool 判断有什么值得注意的点吗? |
答案是不要让对象支持 bool 判断。 |
- Scope Guard:这个我在做 15-445 的时候就搞了很多,结果是反而搞复杂了... 等于如果你要让 throw 之后保证资源释放,那就做一个 RAII guard,但是如果正确执行过程中,资源又有可能释放,所以要加一个 release。只能说是为了保障异常安全吧。如果正常路径一般不需要释放的话,那倒不如在 catch 中做,然而问题是 throw 是我们 throw 的,我们不 catch。还有一个方法就是我们可以 throw 之前自己把资源处理完,但是我们可能在很多个地方 throw.... 看着办吧。
- shrink to fit:C++ 11 之后让 vector 等容器(顺序)支援了这个,避免了内存泄漏(monotonic memory resource 了属于是)。
- small object/buffer optimization: 不说了,一般来说就是优化到你的 control block 的大小,两个机器指针大小之类的?
- Thin Template:提取所有不依赖于模板类型的独立一个基类出来,避免模板膨胀太多。
模板在文件中是每个类型都生成一份吗?这样如何减少代码段大小开销? |
支持 LTO 的编译器会在链接时对同一个类型的进行 weak symbol 的合并,不同类型的都会生成一份独立的,采用 thin template idiom 减少代码膨胀。 |
- Type erasure:也就那样。基本思路就是用类似虚函数/函数指针,然后基类指针。具体我不说了。
你知道 std::function 是怎么实现的吗? |
简简单单报个菜名,通用函数指针(lambda、operator() 等都可以用函数指针的语义实现,即,解引用,然后调用,这个可以模板化为一个函数),可变模板参数,指针,动态内存分配(比如用于复制 stateful 的 object operator()、lambda object 等)+virutal clone。 |
- Type selection:用模板元编程来决定特化生成的类的成员类型(用 std::conditional),比如是用 long 还是用 int,std::bitset 可以用, 只是要求参数是 constexpr 而已。
- Vitual Ctor/virtual clone:神奇的一点就是,虽然 ctor 不能是 virtual,但是我们可以用工厂模式的来实现这种需求。比较重要的用途在于多态场景下的 virtual clone ,当然我们肯定不需要 virtual copy ctor 因为... 你不会用 *(obj1) = *(obj2) 这种做法来写代码,which is error-prone + 难读的。
构造函数可以为虚吗?如果我要根据父类指针复制对象,怎么实现? |
那当然是学 Java 实现一个虚的 clone 方法啦! |
- virtual friend:一样的套路,让 friend 函数调用一个虚函数实现,而不是直接写逻辑。(如果一个中间层不够,就再加几层!)
- capability query:用 dynamic cast 检查 interface 设计的多继承。但是 dynamic cast 需要开启 rtti,而且没有虚函数的用不了。并且,RTTI 也是同样的需要至少一个虚函数。不过,这个限制就是当你把 dtor 写成 virtual 的时候就自动满足了。对于一些情况,与其走这个虚表查询,不如你自己做一个 virtual flag 在基类.... OOP 罪恶。可以用 double dispatch 避免 instanceof/dynamic_cast (但是 GoF 中用双分派加强耦合是因为当时 C++11 没出,没有 RTTI)。我认为不如用一个 virtual flag/enum 了。但是 dynamic_cast 又是必不可少的,只要用到 interface based polymorphism(即多继承体系,以及连续继承等),而 type flag/virtual falg 只支持类似于 abstract class、单一 interface 多个不同子类等,即超绝耦合,父类需要知道子类的信息(当然,用一种额外的 id 的话确实可以不修改父类,总之方法有很多啦)。
你知道 dynamic_cast 的实现原理吗? |
我们并不知道实际是怎么实现的除非我们看了源码,但是我们确实有实现的思路。首先编译器完全知道继承的体系结构,因此只要知道了 most derived class,就能知道一路上的所有类型,因此 type_info 只需要有 most derived class,就足够了,剩余的信息编译期就能生成。 |
visitor 模式示例代码,但是无 instanceof visitor 模式最大的问题是他耦合太强了。但是,其实并没有耦合,因为我们
可以重载 accept 函数
// 客户端代码 foreach (Node node in graph) node.accept(exportVisitor) // 城市 class City is method accept(Visitor v) is v.do(this) // ... // 工业区 class Industry is method accept(Visitor v) is v.do(this) // ... |
- checked delete:delete 必须要看得到析构函数,in-complete type 上调用 delete 是 UB(最根本是他甚至不知道 dtor 是不是 public 的,可不能 ub 吗)。
delete 有什么值得注意的地方吗? |
有的,一个比较常见的八股文是说数组,因为有些编译器实现是在 malloc 出来的内存地方放一个数组尺寸,而且如果 delete 默认支持数组,就不再是零开销抽象了。第二个就是这个 UB。 |
- Coercion by member template:C++ 默认支持子类到父类指针转换,但是不支持模板的。则是因为如果模板不是多态语义使用模板参数类型的话,是实现不了的。如果确实是指针语义,一般都要支持这个,自己写一些运算符重载和 copy assign 的东西。比如智能指针肯定要支持啊(虽然 unique 只能支持 move)。。。
motivation 示例代码,具体实现就不说了,就是写成员函数:
class B {}; class D : public B {}; template <class T> class Helper {}; B *bptr; D *dptr; bptr = dptr; // OK; permitted by C++ Helper<B> hb; Helper<D> hd; hb = hd; // Not allowed but could be very useful |
总结以下:
引用
Polymorphism is not a property of the type, but rather a property of how it is used.
static 变量的构造时机
知识点
ADL: argument dependent lookup
AOP, Aspect Oriented Programming/面向切面编程
C++ 中的 static
C++ 中构造 try catch block
dynamic library 中的变量
ipp 文件的用途
你知道 dynamic_cast 的实现原理吗?
问题
delete 有什么值得注意的地方吗?
friend 函数是不是破坏了封装吗?
operator 函数重载的时候用成员函数、friend 和在 namespace 中写有什么区别和优缺点?
STL 的 allocator 是怎么让不同类型的也能分配内存的?比如我给 map<int> 传一个 std::allocator<int>, 分配节点的内存的时候怎么能通过 int allocator 分配内存呢?
type a = f(), 怎么让 f 支持不同的 type ?(这个不能算一个问题吧...)
单例模式在不同翻译单元中是同一个吗?
构造的时候怎么知道构造哪个成员对象时抛出了异常?
构造函数可以为虚吗?如果我要根据父类指针复制对象,怎么实现?
构造函数中可以对外暴露 this 指针吗?为什么不能。(废话)
基类调用虚函数的时候调用的是谁的实现?如果在构造函数调用呢?如果在析构函数中调用呢?(这不是 UB)
基类需要用到派生类的成员,怎么把派生类的成员传过去?/如何实现先初始化类的成员再初始化基类?
空类的大小是多少?如果要用到很多空类,怎么优化?
模板编程里面怎么让 1 和 2 变成不同的类型?
模板在文件中是每个类型都生成一份吗?这样如何减少代码段大小开销?
你知道 dynamic_cast 的实现原理吗?
你知道 std::function 是怎么实现的吗?
你知道 vptr 是什么时候填充的吗?(先问: vtable 是什么时候初始化的?
全局变量什么时候初始化?全局变量的初始化顺序是怎么样的?析构呢?
让对象支持 bool 判断有什么值得注意的点吗?
如果我需要查询大量 key 和 value 的相互查询,怎么优化?
如果我需要在程序执行之前初始化一系列的对象,怎么初始化?
如果我需要在程序执行之前执行一段程序,怎么实现?
如果我要运行时决定运算的表达式,运行时修改程序代码,怎么做?
如果我要在每次对象的成员函数调用之间插入一些代码,怎么实现?
如果我在每个函数返回的地方要加一些代码,怎么实现?
如何控制 friend 类能够看到的成员?/如何对 friend 的视图做权限限制?
有一个很大的对象可能需要多个线程用到,大部分是读,少部分需要修改,怎么优化?
怎么从容器类里面删除元素?(std::remove 怎么实现的?)/这个根本不算一个能问的问题...
怎么实现 lazy evaluation?
怎么限定一个类只能堆上创建?怎么限定一个类只能栈上创建?
要访问的网站
More C++ Idioms - Wikibooks, open books for an open world
Better Code: Runtime Polymorphism - Sean Parent (still Adobe Sean Parent NDC 2017)
c++ - How is std::function implemented? - Stack Overflow
https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter
Break-able named scopes in C/C++ - Stack Overflow
Inheritance Is The Base Class of Evil (Adobe Sean Parent 2003)
More C++ Idioms/Inner Class - Wikibooks, open books for an open world
暂时标记为完结 2022/5/28 15:56
This page is generated by OneNote.