[翻译] Effective C++, 3rd Edition, Item 20: 用 pass-by-reference-to-const(传给 const 引用)取代 pass-by-value(传值)(上)

C++:传 const 引用取代传值的优势
本文介绍在 C++ 中,缺省以传值方式将对象传入或传出函数代价高昂,如传递 Student 对象会多次调用构造和析构函数。而传 const 引用可避免这些调用,还能避免切断问题,如在图形窗口系统中,传值会使派生类特性被切断,传 const 引用可解决该问题。
部署运行你感兴趣的模型镜像

Item 20: 用 pass-by-reference-to-const(传 const 引用)取代 pass-by-value(传值)

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.youkuaiyun.com/fatalerror99/

缺省情况下,C++ 以 by value(传值)方式将 objects 传入或传出函数(这是一个从 C 继承来的特性)。除非你特别指定其它方式,否则 function parameters(函数形参)就会以 copies of the actual arguments(当前实参的拷贝)进行初始化,而且函数的调用者会取回函数返回值的一个 copy(拷贝)。这些拷贝由 objects 的 copy constructors(拷贝构造函数)生成。这就使得 pass-by-value(传值)成为一个代价不菲的操作。例如,考虑如下 class hierarchy(类继承体系):

class Person {
public:
  Person();                          // parameters omitted for simplicity
  virtual ~Person();                 // see Item 7 for why this is virtual
  ...

private:
  std::string name;
  std::string address;
};

class Student: public Person {
public:
  Student();                         // parameters again omitted
  virtual ~Student();
  ...

private:
  std::string schoolName;
  std::string schoolAddress;
};

(原文并没有显式声明 Student destructor 为 virtual,根据作者网站勘误修改——译者注。)

现在,考虑以下代码,在此我们调用一个函数 validateStudent,它得到一个 Student argument(实参)(以 by value(传值)方式),并返回它是否有效的结果:

bool validateStudent(Student s);           // function taking a Student
                                           // by value

Student plato;                             // Plato studied under Socrates

bool platoIsOK = validateStudent(plato);   // call the function

当这个函数被调用时会发生什么呢?

很明显,Student copy constructor(拷贝构造函数)被调用,用 plato 来初始化 parameter(形参)s。同样明显的是,当 validateStudent 返回时,s 就会被销毁。所以这个函数的 parameter-passing cost(参数传递成本)是一次 Student copy constructor(拷贝构造函数)的调用和一次 Student destructor(析构函数)的调用。

但这还不是全部。一个 Student object 内部包含两个 string objects,所以每次你构造一个 Student object 的时候,你也必须构造两个 string objects。一个 Student object 还要从一个 Person object 继承,所以每次你构造一个 Student object 的时候,你也必须构造一个 Person object。一个 Person object 内部又包含另外两个 string objects,所以每个 Person 的构造也承担着另外两个 string 的构造。最终,以 by value(传值)方式传递一个 Student object 的后果就是导致一次 Student copy constructor(拷贝构造函数)的调用,一次 Person copy constructor(拷贝构造函数)的调用,以及四次 string copy constructor(拷贝构造函数)调用。当 Student object 的拷贝被销毁时,每一次 constructor call(构造函数调用)都有一次 destructor call(析构函数调用)对应,所以以 by value(传值)方式传递一个 Student 的全部成本是六次 constructors(构造函数)和六次 destructors(析构函数)!

好了,这是正确的和值得的行为。毕竟,你想要你的所有 objects 都得到可靠的初始化和销毁。尽管如此,如果有一种办法可以绕过所有这些构造和析构过程,那就更好了,这就是:pass by reference-to-const(传 const 引用):

bool validateStudent(const Student& s);

这样做非常有效:没有 constructors(构造函数)和 destructors(析构函数)被调用,因为没有新的 objects 被创建。被修改的 parameter declaration(形参声明)中的 const 是非常重要的。validateStudent 的最初版本以 by value(传值)方式取得一个 Student parameter(形参),所以调用者知道它们屏蔽了函数对它们传入的 Student 的任何可能的改变;validateStudent 只能改变它的一个 copy(拷贝)。现在 Student 以 by reference(传引用)方式传递,将它声明为 const 也是必要的,否则调用者必然担心 validateStudent 改变了它们传入的 Student

以 by reference(传引用)方式传递 parameter(形参)还可以避免 slicing problem(切断问题)。当一个 derived class object(派生类对象)作为一个 base class object(基类对象)(以传值)方式被传递,base class copy constructor(基类拷贝构造函数)被调用,而那些使得 object 的行为像一个 derived class object(派生类对象)的特殊特性被 "sliced"(“切断”)了。只给你留下一个 simple base class object(纯粹基类对象)——这没什么可吃惊的,因为是一个 base class constructor(基类构造函数)创建了它。这几乎绝不是你想要的。例如,假设你在一组实现一个图形窗口系统的 classes 上工作:

class Window {
public:
  ...
  std::string name() const;           // return name of window
  virtual void display() const;       // draw window and contents
};

class WindowWithScrollBars: public Window {
public:
  ...
  virtual void display() const;
};

所有 Window objects 都有一个名字,你能通过 name 函数得到它,而且所有的窗口都可以显示,你可一个通过调用 display 函数来做到这一点。display 为 virtual(虚拟)的的事实告诉你:一个 simple base class(纯粹基类)的 Window objects 的显示方法有可能不同于专门的 WindowWithScrollBars objects 的显示方法(参见 Items 3436)。

现在,假设你想写一个函数打印出一个窗口的名字,并随后显示这个窗口。以下这个函数的写法是错误的:

void printNameAndDisplay(Window w)         // incorrect! parameter
{                                          // may be sliced!
  std::cout << w.name();
  w.display();
}

考虑当你用一个 WindowWithScrollBars object 调用这个函数时会发生什么:

WindowWithScrollBars wwsb;

printNameAndDisplay(wwsb);

parameter(形参)w 将被作为一个 Window object 构造——它是以 by value(传值)方式被传递的,记得吗?而且使 wwsb 表现得像一个 WindowWithScrollBars object 的特殊信息都被切断了。在 printNameAndDisplay 中,全然不顾传递给函数的那个 object 的类型,w 将始终表现得像一个 class Window 的 object(因为它就是一个 class Window 的 object)。特别是,在 printNameAndDisplay 中对 display 的调用将 always(总是)调用 Window::display,绝不会是 WindowWithScrollBars::display

绕过 slicing problem(切断问题)的方法就是以 by reference-to-const(传 const 引用)方式传递 w

void printNameAndDisplay(const Window& w)   // fine, parameter won't
{                                           // be sliced
  std::cout << w.name();
  w.display();
}

现在 w 将表现得像实际传入的那种窗口。

(本篇未完,点击此处,接下篇)

您可能感兴趣的与本文相关的镜像

Linly-Talker

Linly-Talker

AI应用

Linly-Talker是一款创新的数字人对话系统,它融合了最新的人工智能技术,包括大型语言模型(LLM)、自动语音识别(ASR)、文本到语音转换(TTS)和语音克隆技术

基于 NSFW Model 色情图片识别鉴黄 后面更新视频检测 项目背景: 随着互联网的快速发展,网络上的信息量呈现出爆炸式的增长。然而,互联网上的内容良莠不齐,其中不乏一些不良信息,如色情、暴力等。这些信息对青少年的健康成长和社会风气产生了不良影响。为了净化网络环境,保护青少年免受不良信息的侵害,我国政府加大了对网络内容的监管力度。在此背景下,本项目应运而生,旨在实现对网络图片和视频的自动识别与过滤,助力构建清朗的网络空间。 项目简介: 本项目基于 NSFW(Not Safe For Work)Model,利用深度学习技术对色情图片进行识别与鉴黄。NSFW Model 是一种基于卷积神经网络(CNN)的图像识别模型,通过学习大量的色情图片和非色情图片,能够准确地判断一张图片是否含有色情内容。本项目在 NSFW Model 的基础上,进一步优化了模型结构,提高了识别的准确率和效率。 项目功能: 色情图片识别:用户上传图片后,系统会自动调用 NSFW Model 对图片进行识别,判断图片是否含有色情内容。如果含有色情内容,系统会给出相应的提示,并阻止图片的传播。 视频检测:针对网络视频,本项目采用帧提取技术,将视频分解为一帧帧图片,然后使用 NSFW Model 对这些图片进行识别。如果检测到含有色情内容的图片,系统会给出相应的提示,并阻止视频的传播。 实时监控:本项目可应用于网络直播、短视频平台等场景,实时监控画面内容,一旦检测到含有色情内容的画面,立即进行屏蔽处理,确保网络环境的纯洁。
### 如何在本地部署 NSFW 模型或服务 要在本地环境中成功部署 NSFW(不适宜工作场合内容)检测模型或服务,以下是详细的说明: #### 准备环境 为了确保能够顺利运行模型和服务,需要安装必要的依赖项。这些工具和库包括但不限于以下几类: - **Python 环境**: 推荐使用 Python 3.7 或更高版本。 - **Transformers 库**: 提供加载预训练模型的功能[^1]。 - **PyTorch/TensorFlow**: 支持深度学习框架的计算需求。 - **Pillow (PIL)**: 处理图像文件并将其转换为适合输入模型的形式。 具体命令如下所示: ```bash pip install transformers torch Pillow ``` #### 加载模型与测试 通过 Hugging Face 的 `transformers` 工具包可以直接访问已有的 NSFW 图片分类模型。例如,可以采用名为 `"Falconsai/nsfw_image_detection"` 的公开模型来完成此任务[^1]。 下面是一个简单的代码片段展示如何加载该模型并对单张图片执行预测操作: ```python from PIL import Image from transformers import pipeline def classify_nsfw(image_path): # 打开指定路径下的图片文件 img = Image.open(image_path) # 初始化 image-classification 流水线对象,并指明使用的特定模型名称 classifier = pipeline("image-classification", model="Falconsai/nsfw_image_detection") # 对传入的图片调用流水线方法得到其类别标签及其置信度分数列表形式的结果 result = classifier(img) return result if __name__ == "__main__": test_img_path = "<your_test_image>" output_results = classify_nsfw(test_img_path) print(output_results) ``` 注意替换 `<your_test_image>` 成实际存在的图片绝对或者相对地址字符串值之前再尝试运行以上脚本。 #### 构建 RESTful API 服务 如果希望进一步扩展功能至 Web 应用程序层面,则可考虑利用 Flask/Django 这样的轻量级 web 开发框架构建起支持 HTTP 请求交互的服务端接口。这里给出基于 FastAPI 实现的一个简单例子作为示范用途: ```python import uvicorn from fastapi import FastAPI, File, UploadFile from PIL import Image from io import BytesIO from typing import List from pydantic import BaseModel app = FastAPI() class Prediction(BaseModel): label: str score: float @app.post("/predict/", response_model=List[Prediction]) async def predict(file: UploadFile = File(...)): try: contents = await file.read() pil_image = Image.open(BytesIO(contents)) clf_pipeline = pipeline('image-classification', model='Falconsai/nsfw_image_detection') predictions = clf_pipeline(pil_image) formatted_preds = [{"label": pred['label'], "score": round(pred['score'], 4)} for pred in predictions] return formatted_preds except Exception as e: raise ValueError(f"Error processing uploaded file {e}") if __name__ == '__main__': uvicorn.run(app, host='0.0.0.0', port=8000) ``` 启动服务器之后即可向 `/predict/` 路径发送 POST 请求附带上传待分析的目标图片获取返回结果了。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值