CppCon 2017 学习:Free Your Functions!

“Free Your Functions!” 这句话在C++设计中有很深的含义,意思是:

“Free Your Functions!” 的理解

“解放你的函数”,鼓励程序员:

  • 不要把所有的函数都绑在类的成员函数里
  • 优先考虑写成自由函数(non-member functions),让函数独立于类,
  • 通过类的公共接口操作数据,保持良好封装,
  • 这样能提高代码的灵活性、复用性和可维护性。

为什么“Free Your Functions”很重要?

  • 增强封装性
    自由函数不直接访问私有成员,强迫使用公共接口,维护类的封装。
  • 提高代码复用
    自由函数不依赖特定类的内部实现,更容易用在不同的类或类型上。
  • 减少类接口膨胀
    只把必要的功能放到成员函数,其他辅助功能用自由函数实现,接口更清晰。
  • 支持泛型编程
    自由函数结合模板,能写出高度可复用和灵活的代码。

列出的内容是现代软件设计中非常重要的一组概念,主要围绕 面向对象设计原则(如 SOLID) 和更广义的 良好软件架构目标。下面逐项解释这些术语及它们之间的关系,帮助你全面理解这段内容

软件设计的核心关注点

你开头列出的这些词语可以看作是软件设计要达到的目标

概念说明
Encapsulation(封装)隐藏实现细节,只暴露必要接口,保护对象状态
Abstraction / Polymorphism(抽象/多态)使用接口代替具体实现,允许替换/扩展行为
Cohesion(内聚)一个模块/类只做一件事,且做得好(功能聚焦)
Flexibility / Extensibility系统能轻松适配变化、添加新功能
Reuse / Generality(可复用性 / 泛化能力)代码可复用、适用于多种情况
Testability(可测试性)易于单元测试和集成测试
Performance(性能)程序在资源和时间上运行得高效

假设:“Prefer non-member, non-friend functions”

这个观点出自 “Designing Software with C++”(如 Scott Meyers),含义是:

如果函数不需要访问类的私有成员,应该让它是一个非成员非友元函数。

原因:

  • 避免不必要的耦合(更容易重构)
  • 提高封装性(class 更专注)
  • 更强的内聚(让 class 专注于自己的职责)
  • 更易测试与复用(不依赖内部实现)

SOLID 原则(面向对象设计五大原则)

你列出的 5 条是著名的 SOLID 原则,用于构建可维护、可扩展、高质量的对象导向系统

原则简写含义
Single Responsibility PrincipleSRP一个类应该只有一个引起它变化的原因(只做一件事)
Open-Closed PrincipleOCP软件实体应对扩展开放、对修改关闭
Liskov Substitution PrincipleLSP子类必须能够替换父类,并保持行为正确
Interface Segregation PrincipleISP不要强迫客户依赖他们不用的接口(用多个小接口)
Dependency Inversion PrincipleDIP高层模块不应依赖低层模块,应依赖抽象(依赖注入)

理解整体结构:

这段内容构成了一种 “现代 C++ 架构思维”

  • 我们不只关心语法和实现,更关心:
    • 接口设计是否合理
    • 模块之间是否解耦
    • 系统能否扩展/维护/测试
    • 是否违背了 SOLID 原则
  • 结构上的好设计,比仅仅“代码能跑”更关键

总结一句话:

你这段内容是在强调:

“写好代码不仅要会写,还要懂得如何设计。”

这些设计原则(如封装、SOLID)并不是形式主义,而是为了实现:

  • 可维护
  • 可扩展
  • 高质量
  • 可测试
    的软件系统

这段例子和引用正是 Scott Meyers 在《Effective C++》里强调的一个很重要的设计观点:

将函数设计为非成员函数,有时比成员函数更有利于封装和良好的面向对象设计。

具体分析你的代码

版本1:clearEverything 作为成员函数

class WebBrowser 
{ public: void clearCache(); void clearHistory(); void removeCookies(); void clearEverything() { clearCache(); clearHistory(); removeCookies(); } private: // ... the state 
};

版本2:clearEverything 作为非成员函数

class WebBrowser 
{ public: void clearCache(); void clearHistory(); void removeCookies(); private: // ... the state 
}; 
void clearEverything(WebBrowser& wb) 
{ wb.clearCache(); wb.clearHistory(); wb.removeCookies(); 
}

Scott Meyers 的观点解读

  • 传统观点:既然函数操作的是对象的状态,函数就应该是成员函数,数据和操作绑定在一起更符合面向对象的原则。
  • Scott Meyers 的观点
    • 真正的面向对象的核心是封装(Encapsulation),即把数据隐藏起来,减少暴露。
    • clearEverything() 是成员函数时,它暴露了“WebBrowser 必须有 clearCache、clearHistory、removeCookies”这几个函数,这会使得类接口膨胀,增加耦合。
    • 如果把 clearEverything() 设计为非成员函数,那么它只是使用 WebBrowser 的公共接口,WebBrowser 依然只负责提供最小的、必要的接口。
    • 这样类的接口更紧凑,状态更封装,类的职责更单一,内聚性更强

更宽泛的设计启示

  • “对象导向不是‘函数都放成员函数’”,而是“设计要最大化封装,最小化暴露接口”
  • 非成员非友元函数可以访问公开接口,而不需要访问私有数据,从而减少了类和函数的耦合
  • 这也符合你之前提到的“prefer non-member, non-friend functions”的设计指导

总结

设计方式优点缺点
版本1:成员函数函数和数据在一起,调用方便类接口膨胀,暴露更多成员
版本2:非成员函数保持类接口简洁,减少耦合,增强封装性需要额外写一个辅助函数
你理解的核心点就是:

“面向对象设计的精髓在于‘封装’而不是简单地把所有函数都放到类里。”

这种设计有助于实现 SOLID 原则中 Single Responsibility Principle(单一职责)Interface Segregation Principle(接口隔离)

这组代码展示了不同设计中 耦合度(Coupling) 的体现,以及如何降低类内部成员函数和外部函数的耦合,逐步将实现细节解耦。

代码演变与耦合度分析

版本1:只有数据成员,没有实现

class X 
{ 
public: ... 
private: std::vector<int> values_; 
};
  • 仅有数据成员定义,耦合点还没体现。

版本2:成员函数 doSomething 直接操作 values_,且重置操作内联

class X 
{ 
public: void doSomething(...) { ... // Reset values (直接在这里写) ... } 
private: std::vector<int> values_; 
};
  • doSomething() 中直接操作 values_
  • 重置操作和业务逻辑混在一起,耦合度较高,不易维护。

版本3:引入私有成员函数 resetValues() 来封装重置逻辑

class X 
{ 
public: void doSomething(...) { ... resetValues(); ... } 
private: void resetValues() { for (int& value : values_) value = 0; } std::vector<int> values_; 
};
  • 重置逻辑被封装成私有成员函数,增加了代码的内聚性。
  • resetValues()values_ 强耦合,因为它直接访问 values_

版本4:将重置操作抽象成一个私有函数,传入 values_ 作为参数

class X 
{ 
public: void doSomething(...) { ... resetValues(values_); ... } 
private: void resetValues(std::vector<int>& vec) { for (int& value : vec) value = 0; } std::vector<int> values_; 
};
  • resetValues 仍是成员函数,但操作对象是传入的参数。
  • resetValues 逻辑更通用,但仍是私有,不能被外部复用。

版本5:将 resetValues 彻底移出类,作为一个独立的非成员非友元函数

class X 
{ 
public: void doSomething(...) { ... resetValues(values_); ... } 
private: std::vector<int> values_; 
};
void resetValues(std::vector<int>& vec) 
{ for (int& value : vec) value = 0; 
}
  • 重置逻辑与类完全解耦,降低了类的职责,也使重置函数可复用。
  • 类只负责数据和调用,具体操作实现放到外部,降低了耦合度。
  • 提高了代码的可维护性灵活性

总结:

版本耦合度维护性复用性
版本2高(操作直接写在主函数)低(逻辑混乱)
版本3中高(私有成员函数耦合数据)较好(职责分离)
版本4中(参数化但成员函数)较好
版本5低(独立函数)高(逻辑分离)
这种设计思想体现了**“低耦合,高内聚”**的原则。通过将辅助功能拆分成非成员函数,可以减少类的职责,使类接口更简洁,代码更灵活且容易测试。
你理解的核心就是:

把操作数据的通用逻辑拆出去,作为非成员函数,降低类与操作函数的耦合,提高代码复用和可维护性。

这段代码展示了一个类 X 的不同演化阶段,目的是从高耦合逐步过渡到低耦合,最后提取出通用逻辑成为非成员函数,从而达到更好的设计。以下是对其的逐步理解与分析

目标:降低耦合,提高复用性与可维护性

第1步:数据私有、无逻辑

class X 
{ 
public: ... 
private: std::vector<int> values_; 
};

没有行为逻辑,耦合问题还没有显现。

第2步:逻辑写在成员函数中

class X 
{ public: void doSomething(...) {...// Reset values...}private: std::vector<int> values_; 
};

直接在成员函数中重置 values_代码不清晰、可维护性差

重置逻辑和其他业务逻辑耦合。

第3步:将重置逻辑封装为成员函数

class X 
{ public: void doSomething(...) {...resetValues();...}private: void resetValues() {for (int& value : values_)value = 0;}std::vector<int> values_;
};

逻辑抽取、提高内聚性

resetValues() 强依赖 values_,可重用性差。

耦合仍然存在,但稍微好了一些。

第4步:参数化的成员函数

class X 
{ public: void doSomething(...) {...resetValues(values_);...}private: void resetValues(std::vector<int>& vec) {for (int& value : vec)value = 0;}std::vector<int> values_;
};

resetValues 变得更通用,可以作用于任意 vector。

但它仍然是类的私有成员,外部不可复用。

第5步:提取为非成员函数(低耦合)

class X 
{ public: void doSomething(...) {...resetValues(values_);...}private: std::vector<int> values_;
};
void resetValues(std::vector<int>& vec) {for (int& value : vec)value = 0;
}

最佳方案(低耦合,高复用)

  • 重置逻辑与类本身解耦;
  • 函数可以被其他类或模块复用;
  • X 类不需要关注重置逻辑的实现细节;
  • 符合“单一职责原则”(SRP)与“最少知识原则(LoD)”

总结核心思想

版本重置逻辑位置耦合度复用性可维护性
内联成员函数内部
私有成员函数类中函数封装一般
非成员函数类外独立函数

Scott Meyers 的经典观点:

“Prefer non-member non-friend functions to member functions”

(优先使用非成员、非友元函数,而不是成员函数)

这不仅是封装的强化,更是模块职责分离的体现。
如你所说,“理解”,这部分你理解得非常正确 —— 函数不需要非得绑定在类里,只要不访问内部状态,就可以移出类体,降低耦合、提升模块性

这段代码展示了**“高内聚(Cohesion)”“单一职责原则(SRP, Single Responsibility Principle)”**的对比和实践,下面是详细理解与分析:

关键词解释

高内聚 (Cohesion)

  • 定义:模块内部功能紧密相关,职责单一,彼此协调一致。
  • 目标:让一个类或函数“只做一件事,并做好这件事”。

单一职责原则 (SRP)

  • 一个类应该只有一个导致其变化的原因
  • 意味着:每个类应该专注于一项功能。

对比分析

高耦合版本(低内聚)

class X 
{ public: ...private: void resetValues() { for (int& value : values_) value = 0; } std::vector<int> values_; 
};

问题点:

  • X 类不仅持有 values_,还负责如何“重置”这些值。
  • 重置操作与核心业务混杂,增加了变化点(违背 SRP)。
  • 如果多个类都需要重置 std::vector<int>,逻辑会重复。

高内聚版本(低耦合,遵循 SRP)

class X 
{ public: ...private: std::vector<int> values_; 
};
void resetValues(std::vector<int>& vec) 
{ for (int& value : vec) value = 0; 
}

优点:

  • X 只负责维护它的数据(values_),不关心如何重置。
  • resetValues() 是一个通用函数,可复用于任意 vector。
  • 更符合 SRP:当重置逻辑发生变化时,不需要修改类 X
  • 提高了模块的可重用性、可维护性和可测试性

Scott Meyers(和 Clean Code)哲学的总结:

  • 只要函数不访问类的私有实现,就应该是非成员函数。
  • 内聚性更强的系统更容易理解、扩展和维护。
  • SRP 的目标是分离变化的原因,避免不相关的职责混杂在一个类中。

总结

项目类中 resetValues()类外 resetValues()
Cohesion(内聚)一般
Reuse(复用性)
SRP 满足度违反满足
可测试性一般易测
耦合度

这段代码配合“Flexibility & Extensibility(灵活性与可扩展性)”以及“OCP(Open-Closed Principle,开闭原则)”,传达的是:

理解核心:

开闭原则(OCP)

“软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。”

即:允许扩展其行为,但不应通过修改原有代码来实现。

代码解读:

class X 
{ public: ... private: std::vector<int> values_; 
}; 
void resetValues( std::vector<int>& vec ) 
{ for (int& value : vec) value = 0; 
}

这里体现了:

  • resetValues 函数是类外部的,可对任何 std::vector<int> 使用。
  • 当重置策略需要更改(例如将值设置为 -1,或随机初始化)时,我们不需要修改类 X 本身,只需扩展新的函数即可。

对比修改类内部逻辑的情况:

class X {public:void resetValues() {for (int& value : values_)value = 0;}private:std::vector<int> values_;
};

如果要改为将值设为 -1,你需要修改类的实现,违反 OCP。

更灵活的扩展方式(策略注入):

我们还可以进一步增强灵活性,通过策略函数(函数指针或 std::function

void resetValues(std::vector<int>& vec, std::function<int()> initializer)
{for (int& value : vec)value = initializer();
}

使用方式:

resetValues(x.values_, []{ return 0; });   // 清零
resetValues(x.values_, []{ return -1; });  // 设为 -1
resetValues(x.values_, []{ return rand(); }); // 随机值

这就是“开放扩展(添加新策略)”,而“关闭修改(不动 resetValues 本体)”的典范。

总结

原则 / 特性类外 resetValues() 实现类内实现
遵守开闭原则
灵活策略传入易于传入初始化策略不易扩展
可组合 / 可重用性
耦合性

这段代码意在帮助你理解 软件设计原则中的「复用(Reuse)」与 DRY 原则(Don’t Repeat Yourself)

核心思想:避免重复代码,鼓励复用逻辑

观察代码变化:

class Y 
{ public: // ... private: std::vector<int> indices_; 
}; 
void resetValues( std::vector<int>& vec ) 
{ for (int& value : vec) value = 0; 
}

void reset( std::vector<int>& vec ) 
{ for (int& value : vec) value = 0; 
}
  • 变化仅是函数名从 resetValuesreset,强调它是更通用的逻辑。
  • 可以被多个类(如 XY)调用,来 清零 std::vector 的元素

DRY 原则(Don’t Repeat Yourself)

不要在多个地方编写相同的逻辑

应该将公共逻辑提取出来,集中实现,供多处复用

将清零逻辑抽出为一个非成员函数:

void reset(std::vector<int>& vec);

意味着:

  • XY 都可以使用它。
  • 如果清零逻辑需要更新(如:换成 -1),你只需改一处

实践中更强的复用方式

如果你想复用更多数据结构类型(如 std::arraystd::list 等),可以使用模板:

template<typename Container>
void reset(Container& c) {for (auto& value : c)value = 0;
}

总结

目标实现方式优点
避免重复清零逻辑提取成独立函数 reset()只需写一次,易于维护
支持多个类使用非成员函数可用于任何 std::vector<int> 等容器
将来支持更多容器类型用模板函数提升通用性与复用性
如你所见,这种设计遵循了:
  • DRY(Don’t Repeat Yourself)
  • SRP(Single Responsibility Principle)
  • OCP(Open/Closed Principle)

你这段代码体现了 函数重载(Overloading)静态多态(Static Polymorphism) 的概念,是 C++ 实现多态性(Polymorphism)的基础方式之一。

理解这段代码背后的设计思想

场景:

你希望对不同类型的数据都提供“reset”操作,例如:

  • 单个 int
  • 一个 std::vector<int>(多个 int)

第一种写法(只支持容器)

void reset(std::vector<int>& vec) {for (int& value : vec)value = 0;
}

只定义了对 vector<int> 的 reset,无法用于单个 int 变量

第二种写法(函数重载)

void reset(int& i) {i = 0;
}
void reset(std::vector<int>& vec) {for (int& value : vec)reset(value);  //  复用对 int 的 reset
}

函数重载:同名的 reset 函数根据参数类型不同执行不同的逻辑

重用逻辑vector 版本调用了 int 版本,不再写重复代码

C++ 多态的形式

类型特点
静态多态(编译时)函数重载、模板、运算符重载等
动态多态(运行时)虚函数、继承、多态指针/引用
这里展示的是 静态多态,通过 重载 实现根据参数类型自动选择正确版本的函数。

总结

优点说明
提高代码 可读性reset() 一目了然:对谁都可以 reset
提高 可复用性 / 灵活性多种类型统一接口,便于扩展新类型
符合 OOP 的 多态性原则同一个接口、不同实现
避免代码重复(DRY)复用了 reset(int&) 的逻辑

你这段代码很好地展示了 泛型编程(Generic Programming) 的基本思想 —— 用模板来实现类型无关的逻辑,从而提升代码的 可复用性、灵活性与可扩展性

理解这段代码

void reset(int& i)
{i = 0;
}
template< typename T >
void reset(std::vector<T>& vec)
{for (T& value : vec)reset(value);
}

它做了什么?

  • reset(int&) 是对 单个 int 的处理方式。
  • reset(std::vector<T>&) 是一个泛型模板,适用于 任何元素类型为 T 的 vector
  • 调用 reset(vec) 时,会自动匹配模板并对 vector 中的每个元素调用 reset(value),这又会根据元素类型来选择合适的 reset 重载(例如 int、你之后可以为 std::string 等扩展)。

优点

特性好处
类型无关性(Generic)同一套代码支持 vector<int>vector<double>vector<MyClass>
代码复用性强只需写一次循环逻辑,适配各种 T
配合重载使用,表现静态多态根据元素类型调用不同的 reset(T&) 版本
符合开闭原则(OCP)增加支持新类型时,只需增加对应的 reset(T&) 实现

泛型编程的精髓

这正是 C++ 泛型编程的核心理念:

将算法从类型中抽离出来,用模板参数化类型,从而实现最大限度的复用和灵活性。

示例调用

std::vector<int> v = {1, 2, 3};
reset(v);  // 调用 reset<int>(std::vector<int>&)
int a = 42;
reset(a);  // 调用 reset(int&)

想扩展?

只需添加新的重载版本:

void reset(std::string& s)
{s.clear();
}

然后你就可以写:

std::vector<std::string> vs = {"a", "b", "c"};
reset(vs);  // 会调用 reset(std::string&) 自动清空每个字符串

总结一句话:

你通过 template<typename T> void reset(std::vector<T>&) 实现了 通用容器元素重置逻辑,这是 泛型 + 重载 的优雅结合,完全体现了现代 C++ 风格的设计理念。

你对 Abstraction(抽象) 的理解是正确的,这段代码非常好地体现了 C++ 中“抽象”的核心概念之一:对操作进行抽象,而不是对数据结构硬编码。

这段代码背后的抽象思想

void reset(int& i)
{i = 0;
}
template< typename T >
void reset(std::vector<T>& vec)
{for (T& value : vec)reset(value);
}

什么是“抽象”?

抽象(Abstraction)是将共性提取出来,并屏蔽掉具体细节,只保留“对外可见的操作接口”。
在这段代码中:

  • 你没有关心 vector 中的具体类型(int、double、string、struct …)。
  • 你只关心“这个东西可以被 reset() 操作”。

抽象体现在哪?

层面抽象方式示例/含义
操作(函数层)提供统一的接口 reset(T&)对任何类型都定义“如何被 reset”
数据结构层(容器)模板参数化容器元素类型reset(vector<T>&) 不关心元素是啥,只要能调用 reset(value) 就行
扩展性新增类型只需新增对应的 reset(T&)无需修改已有代码,开放扩展,封闭修改(OCP)

优势总结

优点说明
与数据结构解耦reset 与 vector、int、甚至 struct 都是松耦合的
易于维护和扩展支持新类型仅需扩展 reset(T&),不动原有逻辑
更高层次表达语义你表达的是“这个对象可以 reset”,而不是“这个对象是 int/vec”

对比传统方式(无抽象)

没有抽象时,代码通常是这样:

void resetVectorOfInt(std::vector<int>& vec)
{for (int& i : vec)i = 0;
}

这种方式只能用于 vector<int>不可复用、不可扩展、侵入性强

最终总结一句话:

你将“reset”这个动作独立成了一套统一的操作接口(对不同类型进行抽象),这正是面向对象与泛型编程共同追求的核心设计思想:通过抽象隐藏实现细节,提高复用与扩展能力。

如需更进一步,可以考虑:

  • 添加 reset(MyClass&) 的重载;
  • 或引入概念(C++20 concepts)限制 reset 仅对特定类型有效。

展示的这几种 reset 的实现方式,聚焦在一个非常关键的设计原则上:Testability(可测试性)。你已经抓住了重点 —— 把逻辑从类中解耦出去,使其更容易被单独测试。

理解总结

情况 1:

class X {private:void reset() {for (int& value : values_)value = 0;}std::vector<int> values_;
};
  • 闭合结构,reset 是私有的;
  • 不可单元测试 reset
  • 只能通过类的公共接口间接验证它是否工作,降低了测试精度和粒度。

情况 2:使用 friend 暴露测试

class X {private:void reset() { ... }std::vector<int> values_;friend int testReset(...);
};
  • 测试 hack:通过 friend 提供测试入口;
  • 可以测试,但违反封装性
  • 不建议大规模使用,容易形成隐性依赖

情况 3:使用宏强行修改可见性

#define private public
class X {...
};
  • 危险行为
  • 完全破坏封装性;
  • 虽然可以测试,但这是测试“污染”生产代码的典型反例。

推荐做法:将逻辑移出类

template<typename T>
void reset(std::vector<T>& vec) {for (T& value : vec)reset(value);
}

配合:

class X {private:std::vector<int> values_;public:void clear() {reset(values_);}
};
  • reset() 是一个与 X 无关的通用算法;
  • reset() 本身可以单独测试,不依赖类 X
  • X 只负责提供数据,使用通用逻辑;
  • 完美遵守 SRP、OCP、Testability 原则;
  • 还能提高复用性,Y 类也可以用同一个 reset()

所体现的设计原则

原则如何体现
SRP(单一职责)X 只负责数据管理,reset 负责重置逻辑
可测试性逻辑解耦,无需访问私有成员也能测试 reset
DRYreset 可以被多个类复用
开放封闭(OCP)新增支持类型时,只需要重载 reset(T&)

总结一句话:

reset() 抽离为非成员函数,极大地增强了 代码可测试性模块独立性,这是现代 C++ 中推荐的做法,体现了高内聚、低耦合的架构思想。

展示的两个版本的结构体 S 与函数 f() 的实现,核心是对**性能(Performance)**影响的探讨。我们来理解并分析其中的关键点。

理解比较:两种设计方式

方式一:面向对象成员函数

struct S {float x, y, z;double delta;double compute(); // 成员函数
};
double f() {S s;s.x = /* expensive compute */;s.y = /* expensive compute */;s.z = /* expensive compute */;s.delta = s.x - s.y - s.z;return s.compute();
}

方式二:非成员函数

struct S {float x, y, z;double delta;
};
double compute(S s); // 非成员函数
double f() {S s;s.x = /* expensive compute */;s.y = /* expensive compute */;s.z = /* expensive compute */;s.delta = s.x - s.y - s.z;return compute(s);
}

性能差异分析

比较项面向对象成员函数版本非成员函数版本
调用方式s.compute()(隐式传递 this)compute(s)(显示按值/引用传参)
compute() 参数this(引用)参数 s(可能是值传递)
性能隐患无额外拷贝若按值传参,可能触发结构体拷贝,影响性能
可优化空间内联可能性高若结构体大,建议传引用 compute(const S&)

核心区别:结构体传值 vs 引用

// 推荐写法避免不必要的拷贝:
double compute(const S& s);
  • 避免将 S 拷贝传参,提高效率;
  • 非成员函数更具通用性,可被重用;
  • 可测试性也更强(易 mock / test);

性能优化建议

若要最大化性能与灵活性:

struct S {float x, y, z;double delta;
};
inline double compute(const S& s) {return static_cast<double>(s.x + s.y + s.z + s.delta);
}
double f() {S s;s.x = expensive1();s.y = expensive2();s.z = expensive3();s.delta = s.x - s.y - s.z;return compute(s);
}
  • 函数内联 + 传引用 + 非成员函数:高性能 + 高可读性 + 高可复用性

总结一句话:

从性能角度考虑,非成员函数配合引用传参通常更优,避免不必要的结构体拷贝,并使逻辑更清晰、易测试、易重用。

这组概念:

Encapsulation(封装)

Cohesion / SRP(高内聚 / 单一职责原则)

Flexibility / OCP(灵活性 / 开闭原则)

Reuse / DRY(重用 / 避免重复)

Overloading / Polymorphism(重载 / 多态)

Generic Programming(泛型编程)

Abstraction(抽象)

Testability(可测试性)

Performance(性能)

这些不是“随便提一提的理论”,而是:

现实世界工程中的设计准则和基础工具集

并且它们:

被广泛应用于工业级代码中,如:Chrome、LLVM、Boost、folly、Qt、标准库(STL)、各种游戏引擎等。

示例:真实使用场景

原则真实场景举例
Encapsulation类的私有成员变量;使用接口隐藏实现细节(如 pImpl idiom)
SRP一个类只负责一种职责,比如 Logger 只管日志,不负责配置或输出
OCP多态插件架构,如 Qt 的 signal/slot 或 Web 浏览器的扩展
DRY标准算法库(std::sort, std::accumulate)避免重复代码
Polymorphism虚函数表、接口类,如 folly::Executor 接口
Generic ProgrammingSTL 容器与算法泛型接口(template<typename T>
Abstraction抽象基类、C++20 concepts
Testability将逻辑移出成员函数、用非成员函数便于单元测试
Performance避免拷贝,使用 move 语义、缓存局部性优化

是不是有人真的这么做?

是的:

Google C++ Style Guide

Facebook’s Folly Library

Microsoft C++ Core Guidelines

Scott Meyers(Effective C++ 系列)

Herb Sutter(C++ Core Expert)

都系统性地强调上述原则。你提到的设计思维,是 现代 C++ 开发 的基础框架。

总结

这确实是一个真实且极其重要的设计理念集合,广泛应用于工业实践中。

给出的代码和描述,貌似是在讨论一个叫 reset() 的函数,试图对类 X 中的成员变量 values_(一个 std::vector<int>)做“重置”操作。但代码本身不完整,且有些地方存在问题或不太清晰的地方。

你给出的代码片段和疑惑:

class X 
{ public: ... private: std::vector<int> values_; 
}; 
template<typename T> 
void reset(std::vector<T>) { for (T& value : values_)  // 这里有问题,values_ 是哪个作用域的?reset(value); 
}

主要问题:

  1. reset(std::vector<T>) 是自由函数(非成员函数),但它访问了 values_,这个变量是 X 类的私有成员,无法访问。
  2. 该函数参数是 std::vector<T>,传的是值(拷贝),没有使用引用,且函数体中没有用到传入的参数。
  3. 递归调用 reset(value); ,但是没有看到 reset 针对基本类型 int 的定义,或其它重载。
  4. 类定义两次,内容不完整,可能只是为了表达结构。

猜测和补充说明:

  • 你想写一个模板 reset 函数,针对 std::vector<T> 类型,可以对其中每个元素递归调用 reset,达到“重置”复杂数据结构的效果。
  • X 里有 std::vector<int> values_,你想重置这个 values_,可能是调用 reset(values_);
  • 但是,reset(std::vector<T>) 函数中访问 values_ 是错误的,应当访问函数参数。

一个合理的写法示例:

// 针对基本类型的 reset,简单重置为默认值 0
void reset(int& value) {value = 0;
}
// 针对 std::vector<T> 的模板 reset,递归调用元素的 reset
template<typename T>
void reset(std::vector<T>& vec) {for (T& value : vec) {reset(value);}
}
class X {
public:void resetValues() {reset(values_);  // 调用上面定义的模板 reset 函数}
private:std::vector<int> values_;
};

总结

  • 你的代码中 reset(std::vector<T>) 的参数应该是引用 std::vector<T>&,这样才能修改传入的 vector。
  • 访问 values_ 应该通过参数,而不是直接使用类成员。
  • 递归调用 reset 可以处理嵌套的数据结构。
  • 对于基本类型(如 int),需要定义对应的 reset 函数。

对比和讨论“函数的封装(encapsulation)”问题,特别是关于 reset() 这类重置成员数据的函数应该定义在哪里,是不是“应该封装在类内部”,还是“写成独立的自由函数”?

你给出的几个代码设计思路:

1. 重置函数封装在类内部(私有成员函数)
class X 
{ public: void doSomething(...) { ... resetValues();  // 调用类内部的重置函数... } private: void resetValues() { for (int& value : values_) value = 0; } std::vector<int> values_; 
};
  • 优点:
    • resetValues()X 的私有成员函数,封装性强,外部无法调用。
    • 类内部对成员变量的操作都在类里控制,易维护。
    • 符合面向对象设计原则,数据和操作封装在一起。
  • 缺点:
    • resetValues() 只适用于 X 类内部,不具备复用性。
2. 重置函数写成独立的自由函数,传递成员变量引用
void resetValues(std::vector<int>& vec) 
{ for (int& value : vec) value = 0; 
}
class X 
{ public: void doSomething(...) { ... resetValues(values_);  // 调用外部重置函数... } private: std::vector<int> values_; 
};
  • 优点:
    • resetValues 是通用函数,可以作用于任何 std::vector<int>,具有复用性。
    • 函数和数据分离,关注点明确。
  • 缺点:
    • resetValues 不是 X 类的成员,外部可以调用,有时会破坏封装。
    • 调用者需要传递成员变量,增加调用负担,且类的内部实现暴露给外部代码(至少需要知道成员变量名称)。
3. 只声明类和成员变量,强调“函数应该封装”
class X 
{ public: void doSomething(...); private: std::vector<int> values_; 
};
  • 这强调设计理念:“函数应该封装”,意味着对成员变量的操作,最好由类内部的成员函数来完成

你总结的观点

  • “This is just a reset() function!” 可能是在说,reset() 只是一个普通函数。
  • “Functions should be encapsulated!” 则强调:函数(特别是操作类内部状态的函数)应该封装在类内部,隐藏实现细节,避免外部直接操作成员变量。

总结

设计方式优点缺点适用场景
成员函数封装(如私有resetValues封装好,安全,符合OOP原则不易复用操作复杂成员,注重封装时使用
外部自由函数(resetValues(vec))复用性强,代码简洁破坏封装,外部可直接操作成员变量工具函数,或者多个类共享操作时
你这段话体现的是软件设计中封装的思想:
面向对象的好处之一,就是把数据和对数据的操作封装到一个单元里(类),避免外部随意访问和修改。

内容反映了C++设计和实践中一个很经典、也很有争议的话题:成员函数(member functions)与自由函数(free functions)之间的权衡,以及调用查找机制(lookup)带来的问题。下面我帮你分析和解读这些观点和错误信息。

核心观点解读

1. “IDEs 对成员函数支持更好,对自由函数支持不好”

  • 现代IDE(如Visual Studio、CLion等)通常对类的成员函数有很好的自动补全、跳转和提示支持。
  • 但对自由函数(特别是通过Argument-Dependent Lookup,ADL调用的自由函数)支持往往较差,自动补全不太智能,因为自由函数可能定义在命名空间、模板、不同头文件里。

2. “为什么要写 begin(c)c.begin()

  • C++标准库中,c.begin()是成员函数,begin(c)是自由函数重载。
  • 统一用 begin(c) 好处是可以对所有容器和范围统一调用,也可以适配原始数组等没有成员函数的类型。
  • 但这两者必须都写,给代码带来负担。

3. Bjarne Stroustrup 关于成员函数与自由函数的观点

  • Bjarne在2014年提出让调用x.f(y)也能找到自由函数f(x,y),让语法更统一。
  • 但这个想法遇到强烈反对,部分人认为这违背了面向对象(OO)的设计思想,甚至批评为“卖给了OO”。

4. “程序员更容易找到成员函数而非自由函数”

  • 成员函数直接跟类型绑定,IDE能更容易分析和提示。
  • 自由函数则需要ADL(argument-dependent lookup)来查找,复杂且不直观。

5. ADL 也带来了麻烦

  • 自由函数的查找会依据参数类型所在的命名空间自动展开查找,有时导致意外重载或查找失败。

具体的代码错误示例分析

std::complex<double> a, b;
a.min(b);  // 错误:std::complex没有成员函数min
  • std::complex类没有min成员函数,所以编译器报错:no member named 'min'.
min(a, b);  // 编译错误,原因在于std::min要求参数可比较
  • 调用 std::min(或全局 min)时,编译器会尝试用 < 操作符比较 ab
  • std::complex 没有定义 < 运算符,导致编译错误。
  • 因此,不能直接用 min 来比较两个复数。

结合上下文的理解

  • 成员函数调用的优势: a.min(b) 意味着直接调用 a 对象的成员函数,编译器和IDE易于解析,但类需要事先定义该成员函数。
  • 自由函数的灵活性: min(a,b) 是自由函数调用,编译器通过ADL查找适合的min函数,但需要类型支持比较操作,且IDE难以准确追踪。
  • 设计哲学冲突: OO倾向成员函数封装,泛型和算法库倾向自由函数和重载,二者结合产生矛盾。

结论

  • C++里成员函数调用语法(a.f())更易被IDE支持和理解,但限制灵活性。
  • 自由函数和ADL提升了泛型编程的表达力,但增加了查找复杂度和易用难度。
  • std::complex没有定义 <,所以无法直接用 std::min,这反映出设计时的权衡:复数数学中<没定义,自然不支持。
  • 这是C++语言设计中“成员函数vs自由函数”权衡的经典例子,也说明了程序员在设计接口时必须考虑的可用性和封装问题。

这段话和代码涉及的是C++设计中的两个重要概念的结合:虚函数(virtual functions)自由函数(free functions)结合Argument-Dependent Lookup(ADL)的局限性与应用场景

逐步解读

1. “In combination with ADL free functions mean trouble!”

  • ADL(Argument-Dependent Lookup)是C++中查找函数重载的一种机制,它会在调用自由函数时,自动根据参数类型所在命名空间查找函数。
  • 但这种机制复杂且有时会导致歧义和查找失败,尤其是在涉及虚函数和类继承时,会带来不易发现的问题。

2. “I’m using virtual functions, so I cannot use this idea!”

  • 虚函数是面向对象多态性的基础,可以让派生类覆盖基类实现,实现动态绑定。
  • 但虚函数必须是成员函数,且它们的调用通过对象的vtable完成,不适合用自由函数直接替代或覆盖。

3. 代码示例:

class X 
{ public: virtual void print(std::ostream& os) { // 基类打印实现 os << "X base print\n";} virtual ~X() = default; // 通常虚析构保证多态正确释放
}; 
std::ostream& operator<<(std::ostream& os, const X& x) 
{ x.print(os);  // 调用虚函数实现多态打印return os;
}
  • print 是类 X 的虚成员函数,允许派生类重写,实现多态打印行为。
  • operator<< 是一个自由函数,重载了输出流操作符,用于方便打印 X 及其派生对象。
  • 这个 operator<< 通过调用 x.print(os),实现了统一接口,并利用了虚函数的动态绑定机制。

4. “Use free functions in order to …”

  • … wrap virtual function calls
    • 自由函数(例如operator<<)包装了虚函数调用,提供了统一、简洁的接口。
    • 用户可以写 std::cout << x; 而不是调用成员函数 x.print(std::cout);
  • … get a homogeneous interface
    • 通过自由函数统一接口,可以对不同类型实现统一的调用方式(如统一打印),而无需暴露成员函数细节。
    • 这让用户代码更统一,且利于扩展。

总结理解

  • 虚函数必须是成员函数,不能用自由函数替代它们的多态行为。
  • 自由函数(比如operator<<)可以作为对外统一接口,将调用委托给内部虚函数实现。
  • 这样既能利用虚函数的多态优势,也能提供统一、方便的外部接口。
  • 同时,也避免了ADL在虚函数多态情况下的复杂性,因为多态依赖的是成员函数表,而不是自由函数查找。

“这是不是意味着我们应该转向函数式编程?”结合你给出的代码,反映的是现代C++中用自由函数、模板递归对数据结构做操作,这和函数式编程(functional programming, FP)的一些思想确实有相似之处,但不是完全等同。

我帮你理清思路:

你的代码简要说明:

void reset(int& i) {i = 0;
}
template<typename T>
void reset(std::vector<T>& vec) {for (T& value : vec)reset(value);  // 递归调用 reset,针对不同类型做不同重置
}
  • 这是典型的**泛型编程(generic programming)**风格:
    使用自由函数 reset,通过模板递归,对各种类型进行统一的“重置”操作。
  • 代码对数据结构的操作是函数式风格,即用函数对数据“变换”而不是面向对象那样把操作绑定到类里。

函数式编程 (FP) vs 你这段代码

方面你的代码典型函数式编程特点
数据不可变性,传引用,直接修改数据,数据不可变,操作返回新值
函数是“一等公民”部分体现,通过自由函数实现操作是,函数作为值传递和组合
递归和高阶函数有,递归调用模板函数 reset是,高阶函数和递归是核心
状态和副作用有,reset修改传入变量函数通常无副作用
封装和模块化通过函数分离数据和操作通过函数组合和纯函数分离关注点

结论

  • 你的写法借鉴了函数式编程的“函数+递归”的思想,但仍然是命令式风格,有状态和副作用。
  • C++通常是多范式语言,可以结合面向对象、泛型和函数式风格,选择适合场景的方式。
  • 不一定要“转向”纯函数式编程,而是可以在保持高效和灵活的同时,采用函数式编程的一些优秀实践,比如:
    • 用自由函数代替成员函数,增强复用
    • 尽量减少全局状态和副作用
    • 用递归和模板实现结构化操作

这段内容总结了在**多范式编程(multiparadigm)**中,如何权衡“成员函数(member function)”和“自由函数(non-member function)”的设计原则。它给出了一个指导决策流程,帮助判断一个函数到底应该设计成类的成员,还是作为非成员函数存在。

理解这段设计决策的核心思想

1. 如果函数 需要是虚函数(virtual)

  • 必须做成成员函数。
    • 因为虚函数依赖于类的vtable机制,只有成员函数才能实现动态绑定(多态)。
    • 例子:类的行为需要被派生类覆盖时用虚函数。

2. 如果函数是 输入输出操作符(operator>>,operator<<),或者

函数 需要在左侧参数进行类型转换,即左操作数不是类的实例(或不能是隐式转换的类型):

  • 做成非成员函数(自由函数)。
    • 例如,operator<< 的左边通常是 std::ostream&,它不是类的成员,所以只能写成自由函数。
    • 这种设计符合C++惯例,方便重载流操作符。
  • 如果函数需要访问类的私有成员,则:
    • 将该函数声明为友元(friend)。
    • 这样既保持封装,又允许函数访问私有数据。

3. 如果函数可以通过类的 公共接口实现

  • 建议做成非成员函数。
    • 利用已有的成员函数和公共接口实现功能,提升代码复用和模块化。
    • 避免不必要的成员函数膨胀。

4. 其他情况:

  • 做成成员函数。
    • 比如函数需要直接操作类的内部状态,且不能通过公共接口实现。
    • 成员函数更加自然且安全。

总结一句话

只有真正需要虚函数的,才做成员函数;与类型转换、流操作相关,或能用公共接口实现的,做非成员函数(友元函数视情况而定);其余情况做成员函数。

设计意义和好处

  • 平衡封装性与灵活性
    • 保持类接口的简洁性,不让成员函数过多膨胀。
    • 利用自由函数增强代码复用和模块化。
  • 方便操作符重载和泛型编程
    • 操作符重载如<<,做非成员函数更自然。
    • 保证类型转换能在自由函数调用时正常发生。
  • 支持多态行为
    • 虚函数只能是成员函数,保证运行时多态。

这段内容反映了C++中自由函数(free functions)和面向对象编程(OOP)成员函数设计理念之间的经典争论,同时强调了自由函数在设计原则和标准库中的核心价值。

让我帮你详细理解:

核心观点

“But I have learned the opposite! Free functions are just backward and C-style programming!”

  • 有些人认为自由函数是旧式、过程化、C语言风格的写法,不够面向对象。
  • 这种看法源自传统OOP强调将行为封装在对象内部,即成员函数。

反驳:copy() 这类自由函数的设计优势

template<typename InputIt, typename OutputIt>
OutputIt copy(InputIt begin, InputIt end, OutputIt dst_begin)
{// ...
}
  • copy 是C++标准库中的自由函数范例。它操作泛型迭代器,不属于某个类,解耦合、通用且高复用

自由函数设计符合经典设计原则(SOLID)

  • SRP(单一职责原则)
    • 函数专注于一件事,比如 copy 就只负责拷贝数据,不管数据容器如何实现。
  • OCP(开放封闭原则)
    • 代码可扩展(比如支持不同迭代器类型),但不修改已有代码。
  • ISP(接口隔离原则)
    • 函数依赖于精简接口(迭代器概念),避免强耦合复杂接口。
  • DIP(依赖倒置原则)
    • 函数依赖抽象(迭代器抽象),而非具体实现。
      这让自由函数如 copy 能做到高扩展性、高复用性和灵活性

标准模板库(STL)设计的突破性

  • Scott Meyers(《Effective STL》作者)称赞STL设计是高效且可扩展的设计突破
  • STL大量采用自由函数、模板和迭代器的组合,代表了现代C++的最佳设计实践。
  • STL展示了自由函数不仅不落后,反而是泛型编程和现代设计的重要基石

结论

  • 说自由函数是“C风格”其实是一种误解。
  • 自由函数和成员函数各有定位:自由函数提升灵活性和复用,成员函数体现封装和多态。
  • 现代C++倡导多范式编程,善用自由函数,结合面向对象和泛型编程的优势。
  • 标准库函数(如copy)是自由函数设计优秀典范。

这段话是Herb Sutter 和 Scott Meyers 等C++大师推广的设计理念,主张**“尽量使用非成员、非友元函数(free functions)”**,这也是现代C++编程中一个非常重要的指导原则。

理解这句话的核心:

“Free functions are just backward and C-style programming!”

  • 这是对自由函数的一个常见误解,认为它们是老旧的、非面向对象的写法。
  • 但实际上自由函数在现代C++中有更深的设计价值。

Hypothesis & Guideline:

“Prefer non-member, non-friend functions”

  • 假设(Hypothesis): 优先考虑使用非成员、非友元函数,因为它们通常能带来更好的封装和代码复用。
  • 指导原则(Guideline): 在设计时,优先实现自由函数,而不是把所有函数都做成类的成员或友元。

“Free Your Functions!”

  • 这是一个标语,意思是“解放你的函数”,不要把函数都绑在类里。
  • 让函数独立于类,这样可以增强代码的灵活性、模块化和可维护性。

为什么优先考虑非成员、非友元函数?

  • 更好的封装
    通过非成员函数访问类的公共接口,不破坏封装性。
  • 降低耦合
    不依赖类的私有细节,不强制函数绑定到特定类。
  • 提高复用
    自由函数更容易被不同类型重用,尤其是泛型编程。
  • 更清晰的接口设计
    只把必要的操作做成成员函数,其余的放自由函数,保持类接口简洁。

总结

  • 这不是否定成员函数的重要性,而是提醒我们不要滥用成员函数。
  • 对于不需要访问私有数据、不需要虚函数机制的操作,非成员自由函数是更优选择
  • 这也是C++标准库设计成功的一个重要经验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/85626.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/85626.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

日常运维问题汇总-19

60. OVF3维护成本中心与订货原因之间的对应关系时&#xff0c;报错提示&#xff0c;SYST: 不期望的日期 00/00/0000。消息号 FGV004&#xff0c;如下图所示&#xff1a; OVF3往右边拉动&#xff0c;有一个需要填入的字段“有效期自”&#xff0c;此字段值必须在成本中心定义的有…

2025SCA工具推荐︱基于多模态SCA的新一代开源供应链风险审查与治理平台

近年来&#xff0c;随着开源软件在企业数字化转型中的广泛应用&#xff0c;开源供应链攻击事件频发&#xff0c;企业普遍面临三大突出难题&#xff1a;一是不清楚自身引入了哪些开源组件&#xff0c;二是不掌握组件中潜在的安全漏洞和合规风险&#xff0c;三是缺乏自动化、全流…

CppCon 2017 学习:Migrating a C++03 library to C++11 case study

这段内容是在介绍 Wt&#xff08;发音类似 “witty”&#xff09; —— 一个用于 C 的 Web UI 框架。总结如下&#xff1a; 什么是 Wt&#xff1f; Wt 是一个 用 C 编写的 widget&#xff08;控件&#xff09;驱动的 Web 框架。类似于桌面 GUI 框架&#xff08;比如 Qt&#…

coding习惯 + Bug记录整理

&#x1f4d6; 清单 1、包装类型导致的NPE2、xxApiWrapper命名3、see注释4、MySQL模糊匹配特殊字符bug 整理些平时不好的coding习惯导致的bug&#x1f4dd; 1、包装类型导致的NPE 处理项目的一个bug&#xff0c;看日志是发生了空指针&#xff0c;相关代码如下&#xff1a; D…

机器学习项目微服务离线移植

机器学习项目微服务离线移植 引言&#xff1a;为什么需要Docker化机器学习项目&#xff1f; 在当今的机器学习工程实践中&#xff0c;项目部署与移植是一个常见但极具挑战性的任务。传统部署方式面临着"在我机器上能运行"的困境——开发环境与生产环境的不一致导致…

JS红宝书笔记 8.4 类

与函数类型相似&#xff0c;定义类也有两种主要方式&#xff1a;类声明和类表达式&#xff0c;这两种方式都使用class关键字加大括号 与函数表达式类似&#xff0c;类表达式在它们被求值前也不能引用&#xff0c;不过与函数定义不同的是&#xff0c;虽然函数声明可以提升&…

专题:2025游戏科技与市场趋势报告|附130+份报告PDF汇总下载

原文链接&#xff1a;https://tecdat.cn/?p42733 2024年全球游戏市场规模突破1877亿美元&#xff0c;中国以37.5%的全球占比成为核心增长引擎。生成式AI以52%的企业采用率重塑开发流程&#xff0c;混合休闲游戏实现37%的收入增长&#xff0c;跨端互通产品贡献42%增量。玩家行为…

【沉浸式解决问题】Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required

目录 一、问题描述二、场景还原1. 测试mapper2. 测试service 三、原因分析四、解决方案1. DemoApplicationTests2. DemoApplication 后记 一、问题描述 在Application文件中加了ComponentScan注解&#xff0c;此后运行任何测试方法均报错 java.lang.IllegalStateException: Fa…

Ubuntu 和 CentOS 中配置静态 IP

在 Ubuntu 和 CentOS 中配置静态 IP 的方法有所不同&#xff0c;主要因为两者使用的网络管理工具不同。以下是详细步骤&#xff1a; Ubuntu&#xff08;18.04 及更新版本&#xff0c;使用 netplan&#xff09; 1. 查看网卡名称 ip a记录网卡名称&#xff08;如 ens33、eth0&a…

4、做中学 | 二年级下期 Golang整型和浮点型

上篇介绍了哪些数据类型&#xff0c;如何进行声明常量和变量&#xff0c;那么接下来进行详细学习下各个数据类型的使用&#xff01; 一、整数 在go中&#xff0c;整数分为俩类&#xff0c;一个是无符号整数&#xff0c;即0以上数值&#xff1b;另一个是有符号数值&#xff0c;…

C++11 GC Interface:从入门到精通

文章目录 一、引言二、C11简介2.1 C11发展历史2.2 C11新特性概述 三、C11中的垃圾收集支持和基于可达性的泄漏检测3.1 背景与原理3.2 相关标准与接口3.3 示例代码 四、C11 GC interface的使用场景4.1 简化内存管理4.2 提高代码可靠性 五、C23中移除垃圾收集支持和基于可达性的泄…

《高并发系统性能优化三板斧:缓存 + 异步 + 限流》

高并发系统性能优化三板斧&#xff1a;缓存 异步 限流 引言 在互联网应用的高并发场景下&#xff0c;系统性能面临巨大挑战。以某电商平台会员活动为例&#xff0c;活动期间瞬时QPS可达10万&#xff0c;若未进行有效优化&#xff0c;服务器将迅速崩溃。本文从缓存、异步、限…

JVM(4)——引用类型

痛点引入&#xff1a; 为什么需要不同的引用类型&#xff1f;直接只用强引用不行吗&#xff1f;&#xff08;内存泄漏风险、缓存管理粗粒度、对象生命周期监听需求&#xff09; 核心作用&#xff1a; 解释引用类型如何让程序员与垃圾收集器&#xff08;GC&#xff09;协作&…

ONLYOFFICE 文档 9.0 版本已发布:新界面、图表查看器、.md 文件支持、AI 表格与宏等更新

ONLYOFFICE 文档 9.0 版本已正式发布。此次更新包含 20 多项新功能和约 500 项修复&#xff0c;全面提升您的办公效率。从全新界面、突破性的 AI 工具到更广泛的文件格式兼容性&#xff0c;本次发布将带来更加流畅的使用体验。阅读本文&#xff0c;了解详情。 更新全部编辑器的…

关于python-socket服务的问题记录

概述 在使用pythonwebsocket部署socket服务&#xff0c;前端使用小程序来连接&#xff0c;过程中存在以下可能出现的问题&#xff1a; 1&#xff0c;代码里socket端口问题2&#xff0c;服务器配置问题&#xff08;域名解析&#xff1f;Nginx配置是否正确处理了WebSocket升级头…

typescript vs go vs rust

typescript 后端选型&#xff1a; Express &Typescript &trpc 广泛使用&#xff0c;灵活&#xff0c;快速&#xff0c;稳定 Nestjs 企业级&#xff0c;标准化&#xff0c;像java &#xff0c;依赖注入&#xff0c; Hono , web standards framework. Support for any J…

OpenGL和OpenGL ES区别

OpenGL&#xff08;Open Graphics Library&#xff09;和OpenGL ES&#xff08;OpenGL for Embedded Systems&#xff09;都是用于图形渲染的API&#xff0c;但它们的目标平台和设计定位有所不同。 1. 目标平台 OpenGL 主要用于桌面平台&#xff08;如Windows、macOS、Linux&a…

PyTorch 入门之官方文档学习笔记(一)

目录 1 张量 1&#xff09;张量的初始化和属性 2&#xff09;张量操作 3&#xff09;使用 NumPy 进行桥接 2 torch.autograd 1&#xff09;背景 2&#xff09;在 PyTorch 中的使用 3&#xff09;Autograd 的微分机制 4&#xff09;计算图原理 5&#xff09;从计算图中…

King’s LIMS 系统引领汽车检测实验室数字化转型

随着汽车保有量的持续攀升和车龄的增长&#xff0c;消费者对汽车的需求已悄然转变&#xff0c;从最初对外观和性能的追求&#xff0c;逐渐深化为对安全性、可靠性、耐久性、性能与舒适性以及智能化功能的全方位关注。这无疑让汽车检测行业在保障车辆质量、满足市场需求方面肩负…

Neo4j常见语句-merge

merge用法&#xff1a;MERGE 是 Neo4j 中一个强大的操作符&#xff0c;用于确保图中存在特定的节点或关系。它的核心逻辑是&#xff1a;如果目标存在则匹配&#xff0c;不存在则创建 基本语法与逻辑&#xff1a; MERGE <pattern> [ON CREATE <create_clause>] //…