构造函数沉思录

2023-11-30 00:38
文章标签 构造函数 沉思

本文主要是介绍构造函数沉思录,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

缘起

    构造函数,是由C++引入主流程序世界的,其用意是在《C++语言的设计与演化》如是表达:

      它建立起其它成员函数进行操作的环境基础。

    在很早的一篇blog《对象的声明》中,我曾探讨过构造函数的来龙去脉。对于面向对语言而言,构造函数似乎是标配。

    一个语言特性,一旦被扔到真实世界,随之而来的是,其使用往往会超出其设计者的初衷,构造函数亦是如此。

    事实上,通过前面C++之父的描述,我们依然很难定位构造函数的准确用法。所以,我们常常看到许多人把诸多操作强塞入构造函数,造成构造函数极为复杂,进而关于导致了一些复杂的语法讨论,比如如何处理构造函数抛出的异常。

    这里要讨论的是构造函数的另一个常见问题。

    重载构造函数

    同样是在《C++语言的设计与演化》里,有这样一段描述:

       观察发现,允许定义多个构造函数很有价值,因此这也就成了C++重载机制的一个重要应用方面。

    是的,我要说的就是构造函数重载。构造函数可以重载,Java和C#也拿了过来,这似乎成了一种约定俗成。

    在实际应用中,有不少人会这么做:给一个类创建多个构造函数,有的初始化了全部的字段/成员变量,有的只初始化诸多字段/成员变量中的几个。下面便是一个例子:

     public Image(URL url, Tag tag) {

       this.url = url;

       this.tag = tag;

     }

     public Image(URL url) {

       this.url = url;

     }

    这么做的原因通常是,初写这些代码时,这些构造函数要用在不同的场合下。比如,在产品代码中,我们需要的可能是一个完整的对象,而在测试代码中,针对要测试的内容,我们只要设置几个字段即可。

    但是,因为它们都是构造函数,名字完全一致,其最初的意图无法体现,后来的人看到这样的函数,图省事,便拿过来用。随后一些初始化不完整的对象就出现在系统中。随着系统的不断演进,残缺的对象在很多情况下就会出问题,于是,为了修补,我们再向代码里添加一些setter。与setter相伴的往往是,可变(mutable)对象的出现。而许多系统的状态不稳定就是由各种可变对象造成(这也是一个值得讨论的话题)。貌似很简单的构造函数蕴含着诸多的问题。

    就这个问题而言,可以怎样解决呢?

    一种常见的解决方案是,类只提供一个完整的构造函数,至于其它部分,则采用工厂方法完成。比如上面的例子,对Image类,我们只有一个构造函数:

     public Image(URL url, Tag tag) {

       this.url = url;

       this.tag = tag;

     }

    如果在测试里用到,就为它创建一个工厂方法:

     class ImageForTestFactory {

       public static Image createImageWithURL(URL url) {

         return new Image(url, null);

       }

     }

    Image的第二个参数Tag,这里就简单的设置为null,事实上,我们可以根据需要进行设置。比如,我们需要所有的字段都不能为空,这里就可以提供一个缺省的Tag。这段代码的使用者根本无需顾及Tag究竟是怎样。

    工厂方法很大的一个价值,便在于它提供了名字,表明意图。名字到底有多大价值,如果你对整洁代码(Clean Code)有所追求,便就会发现,关于整洁代码的讨论,第一个要讨论的东西便是命名。

    事实上,这些做法并不如何特殊,《Effective Java》第二版,开篇讨论的就是这样的问题。条款1就是“考虑以静态工厂方法代替构造函数”。这个条款里面建议的方案更加激进,建议将构造函数设置为private。这样一来,人们就完全没有机会使用该类的构造函数,只能通过其提供的工厂方法构造对象:

     class Image {

       private Image(URL url, Tag tag) {

         this.url = url;

         this.tag = tag;

       }

       public static Image createNewImage(URL url, Tag tag) {

         return new Image(url, null);

       }

       ...

     }

    另外一种值得考虑的做法是,采用builder模式。《Effective Java》第二版的条款2给出了更详细的解释。所以,如果类里有多于一个的构造函数,那么请考虑其它方式代替。

    无构造函数的Go

    顺着这个思路,再进一步,我们完全可以写出“除本类之外,没有new本类对象的代码”。换句话说,如果不是语言层面有所限制,我们完全可以抛弃构造函数,而事实上,Go语言就这么做了。

    在Go语言里,如果我们要构造一个对象可以这么做:

     type Person struct {

       name string

     }

     func NewPerson(name string)(*Person) {

       p := new(Person)

       p.name = name

       return p

     }

    在语法上,Go语言本身并没有类,但从C/C++的年代我们就知道,struct和class本质是一样的。所以,实际上,NewPerson就是一个构造函数,它负责初始化了Person的相关字段。构造一个对象的方法就可以这样:

     p := NewPerson(\"dreamhead\")

    从本质上说,NewPerson就是一个工厂方法。当然,Go语言之所以可以这么做,因为其struct的所有字段都是public,可以自由访问,在面向对象程序设计语言中,恐怕没那么简单。或许,抛开构造函数的做法,让我们一下子回归到了最初的年代,但同之前模糊的印象不同,如今我们对构造的概念有了全新的理解。我们依然要构造对象,只是不再依赖于构造函数而已。

    是时候反思一下构造函数了。C++设计于80年代,那时候,设计模式还不是主流,那时候,编写代码更多强调的是功能,而非整洁。

这篇关于构造函数沉思录的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/434940

相关文章

C++中第一次听到构造函数

在C++中第一次听到构造函数这个名词,在C#中又遇到了。   在创建某个类时,由于对该对象的状态(数据)不是很明确,因此需要对其进行初始化。比如说我们要在长方形这个类中创建一个对象,或者说新建一个长方形,那么我们首先要确定他的长和宽,假如我们无法确定它的长和宽,那么我们是无法造出一个长方形来的。所以就要使用这个长方形类中一个用来构造该类所有对象的函数--构造函数。由于该函数要在创建一个新对象

《C++中的移动构造函数与移动赋值运算符:解锁高效编程的最佳实践》

在 C++的编程世界中,移动构造函数和移动赋值运算符是提升程序性能和效率的重要工具。理解并正确运用它们,可以让我们的代码更加高效、简洁和优雅。 一、引言 随着现代软件系统的日益复杂和对性能要求的不断提高,C++程序员需要不断探索新的技术和方法来优化代码。移动构造函数和移动赋值运算符的出现,为解决资源管理和性能优化问题提供了有力的手段。它们允许我们在不进行不必要的复制操作的情况下,高效地转移资源

为什么构造函数不能为虚函数

1,从存储空间角度     虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,无法找到vtable,所以构造函数不能是虚函数。 2,从使用角度         虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调

《C++沉思录》-读书随记

一.抽象是有选择的忽略。比如你要驾驶一辆汽车,但你又必须时时关注每样东西是如何运行的:发动机、传动装置、方向盘和车轮之间的连接等;那么你要么永远没法开动这辆车,要么一上路就马上发生事故。编程也依赖于一种选择,选择忽略什么和何时忽略。也就是说编程就是通过建立抽象来忽略那些我们此刻并不重视的因素。(看到这句话,才知道自己平时敲的压根就称不上编程,还有好长的路要走。) 二.如何将一个庞大的编程问题当作

【JavaScrip】为什么箭头函数不能做构造函数

在 JavaScript 中,箭头函数(Arrow Functions)的设计初衷是为了简化函数声明,并引入了一些新的语法特性。其中一个关键特性就是箭头函数不能用作构造函数。下面我们详细探讨这个问题的原因。 1. 箭头函数的特点 箭头函数有一些独特的特点,其中最重要的是: ● 词法作用域的 this: 箭头函数内部的 this 值绑定到定义时所在的上下文环境,而不是调用时的上下文环境。 ● 简

C/C++ 拷贝构造函数

一. 什么是拷贝构造函数 首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: [c-sharp]  view plain copy int a = 100;   int b = a;    而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。 下面看一个类对象拷贝的简单例子。 [c-sharp]  view p

【自用19.1】C++构造函数

构造函数的作用 在创建一个新的对象时,自动调用的函数,用来进行“初始化”工作: 对这个对象内部的数据成员进行初始化。 构造函数的特点 自动调用(在创建新对象时,自动调用)构造函数的函数名,和类名相同构造函数没有返回类型可以有多个构造函数(即函数重载形式)   构造函数的种类 默认构造函数 自定义的构造函数 拷贝构造函数 赋值构造函数 默认构造函数 没有参数的构造函数

第二百一十二节 Java反射 - Java构造函数反射

Java反射 - Java构造函数反射 以下四种方法来自 Class 类获取有关构造函数的信息: Constructor[] getConstructors()Constructor[] getDeclaredConstructors()Constructor<T> getConstructor(Class... parameterTypes)Constructor<T> ge

第七章 构造函数this静态单例模式

7.1 构造函数 构造函数与类名相同,无返回值。 类中未定义构造函数时,默认使用空参构造函数。 7.2 关键字this 使用this可增强代码的可读性 成员变量与局部变量同名时,this.var指当前对象的成员变量。函数中指此函数所在的对象。 7.3 关键字static 可用来修饰成员遍历和函数。 成员被该类所有对象所共享 内存中存在方法区,因此优先于对象存在。 可

类实例化和构造函数

类实例化和构造函数 类如何创立,如何调用构造函数源码rv汇编行为分析 一般成员函数虚函数源码汇编行为分析 纯虚函数汇编行为分析 多态源码汇编行为分析 为什么构造函数不能是虚函数 类如何创立,如何调用构造函数 源码 #include <iostream>using namespace std;// 抽象父类class Base {int a,b;public:Base(){