为什么C ++不允许您使用构造函数的地址?


14

是否有某种特定的原因会在概念上破坏该语言,还是有某种特定的原因在某些情况下在技术上不可行?

用法将与新运算符一起使用。

编辑:我将放弃希望让我的“新操作员”和“新操作员”变得直截了当并保持直率。

问题的关键是:构造函数为何如此特殊?当然要记住,语言规范告诉我们什么是合法的,但不一定是道德的。合法的东西通常由与该语言其余部分在逻辑上一致的东西,简单明了的东西以及编译器可以实现的东西告知。标准委员会权衡这些因素的可能依据是经过深思熟虑的,因此很有趣。


取得构造函数的地址不是问题,但是能够传递类型。模板可以做到这一点。
欣快2014年

如果您有一个函数模板想要使用将被指定为函数参数的构造函数来构造对象,该怎么办?
Praxeolitic

1
我可以想到的任何示例都会有其他选择,但是,为什么构造函数应该特殊?在大多数编程语言中,您可能不会使用很多东西,但是像这样的特殊情况通常带有理由。
Praxeolitic

1
@RobertHarvey当我要输入工厂班级时,我想到了这个问题。
Praxeolitic

1
我不知道C ++ 11 std::make_uniquestd::make_shared能够充分解决这个问题的根本动机实用。这些是模板方法,这意味着需要捕获输入参数到构造函数,然后将它们转发到实际的构造函数。
rwong

Answers:


10

指向成员的指针函数仅在具有相同签名的多个成员函数时才有意义-否则指针将只有一个可能的值。但这对于构造函数是不可能的,因为在C ++中,同一类的不同构造函数必须具有不同的签名。

Stroustrup的替代方法是为C ++选择一种语法,其中构造函数的名称可以不同于类名-但这将避免现有ctor语法的某些非常优雅的方面,并使该语言更加复杂。对我来说,看起来价格很高,只是允许很少需要的功能,可以通过将对象的初始化从ctor“外包”到另一个init函数(可以将指针指向成员的普通成员函数)“轻松”地模拟出来。创建)。


2
不过,为什么要预防memcpy(buffer, (&std::string)(int, char), size)?(可能非常不洁,但这毕竟是C ++。)
Thomas Eding 2014年

3
抱歉,但是您写的没有任何意义。我没有指向成员指向构造函数的指针有任何问题。同样,听起来您引用了某些内容,但没有链接到源。
2014年

1
@ThomasEding:您究竟希望该语句做什么?复制字符串ctor somewgere的汇编代码?如何确定“大小”(即使您尝试使用标准成员函数的等效项)?
布朗

我希望它能像给定一个自由函数指针的地址那样做memcpy(buffer, strlen, size)。大概它将复制程序集,但谁知道。是否可以在不崩溃的情况下调用代码,将需要有关您使用的编译器的知识。确定大小也一样。这将高度依赖平台,但是生产代码中使用了许多不可移植的C ++构造。我认为没有理由将其取缔。
Thomas Eding 2014年

@ThomasEding:当尝试访问函数指针时,如果它是数据指针,则符合要求的C ++编译器将提供诊断信息。不合格的C ++编译器可以执行任何操作,但是它们还可以提供一种非c ++的方式来访问构造函数。这不是在C ++中添加在一致性代码中没有用的功能的原因。
Bart van Ingen Schenau 2014年

5

构造函数是当对象尚不存在时调用的函数,因此它不能是成员函数。它可能是静态的。

在分配内存之后但尚未完全初始化之前,实际上使用this指针调用了构造函数。结果,构造函数具有许多特权功能。

如果您有一个指向构造函数的指针,则它要么必须是静态指针(类似于工厂函数),要么必须是指向在分配内存后立即被调用的特殊指针。它不能是普通的成员函数,而仍可以用作构造函数。

想到的唯一有用的目的是一种特殊的指针,可以将其传递给new运算符,以使其间接使用哪个构造函数。我想这可能很方便,但是它需要大量的新语法,并且可能的答案是:他们考虑了这一点,这是不值得的。

如果您只想重构通用的初始化代码,那么普通的内存函数通常是一个足够的答案,您可以找到其中一个指针。


这似乎是最正确的答案。我记得许多年前(多年前)有关新操作员和“新操作员”内部工作的文章。运算符new()分配空间。新的运算符使用分配的空间调用构造函数。接受构造函数的地址是“特殊的”,因为调用构造函数需要空间。调用这样的构造函数的访问权限是new放置。
Bill Door

1
单词“ exist”掩盖了一个细节,即一个对象可以有一个地址并分配了内存但没有初始化。无论是否使用成员函数,我都认为获取此指针会使函数成为成员函数,因为它显然与对象实例相关联(即使未初始化)。也就是说,答案提出了一个很好的观点:构造函数是唯一可以在未初始化的对象上调用的成员函数。
Praxeolitic

没关系,显然,它们具有“特殊成员功能”的名称。C ++ 11标准的第12条:“默认构造函数(12.1),复制构造函数和复制赋值运算符(12.8),移动构造函数和移动赋值运算符(12.8)和析构函数(12.4)是特殊的成员函数。”
Praxeolitic

12.1:“构造函数不得为虚拟(10.3)或静态(9.4)”。(我的重点)
Praxeolitic 2014年

1
事实是,如果使用调试符号进行编译并查找堆栈跟踪,则实际上存在指向构造函数的指针。我永远无法找到语法来获取该指针(&A::A在我尝试过的任何编译器中均
不起作用

-3

这是因为它们不是构造函数的返回类型,并且您没有在内存中为构造函数保留任何空间。就像您在声明期间使用变量的情况下一样。例如:如果您编写简单变量X,则编译器将生成错误,因为编译器将无法理解其含义。但是当你写Int x时;然后编译器知道它是int类型的data变量,因此它将为变量保留一些空间。

结论:-因此得出的结论是,由于排除了返回类型,因此它将无法获取内存中的地址。


1
构造函数中的代码必须在内存中具有地址,因为它必须在某个地方。无需在堆栈上为其保留空间,但它必须在内存中。您可以使用不返回值的函数的地址。(void)(*fptr)()声明一个没有返回值的函数的指针。
Praxeolitic

2
您错过了问题的重点-原始帖子问有关为构造函数获取代码地址,而不是构造函数提供的结果的问题。另外,在此板上,请使用完整的单词:“ u”不是“ you”的可接受替代。
BobDalgleish

praxeolitic先生,我想如果我们不提及任何返回类型,那么编译器将不会为ctor设置特定的内存位置,并且该位置是在内部设置的。编译器?如果错了,请以正确的答案纠正我
偏爱的Goyal,2016年

并告诉我有关参考变量的信息。我们可以获取参考变量的地址吗?如果否,那么什么地址printf(“%u”,&(&(j))); 如果&j = x,其中x = 10,则在打印吗?因为printf打印的地址和x的地址不相同
偏爱Goyal,2016年

-4

我会做出一个疯狂的猜测:

C ++构造函数和析构函数根本不是函数:它们是宏。它们被内联到创建对象的范围和销毁对象的范围中。反过来,没有构造函数或析构函数,对象只是IS。

实际上,我认为该类中的其他函数也不是函数,而是DONT会内联的内联函数,因为您要获取它们的地址(编译器意识到您已经进入了该内联函数,而不是内联或内联代码到函数中,优化该功能),然后该功能似乎“仍然存在”,即使您未解决该问题也不会。

C ++“对象”的虚拟表与JavaScript对象不同,您可以在其中获取其构造函数并在运行时通过来创建对象new XMLHttpRequest.constructor,而是指向匿名函数的指针的集合,这些指针充当与该对象进行接口的方式,不包括创建对象的能力。而且“删除”对象甚至没有意义,因为就像试图删除一个结构一样,您不能:它只是一个堆栈标签,只需在另一个标签下随意写就可以:使用一个类作为4个整数:

/* i imagine this string gets compiled into a struct, one of which's members happens to be a const char * which is initialized to exactly your string: no function calls are made during construction. */
std::string a = "hello, world";
int *myInt = (int *)(*((void **)&a));
myInt[0] = 3;
myInt[1] = 9;
myInt[2] = 20;
myInt[3] = 300;

没有内存泄漏,没有问题,除了您实际上浪费了为对象接口和字符串保留的一堆堆栈空间,但是这不会破坏您的程序(只要您不尝试使用它)再次作为字符串)。

实际上,如果我以前的假设是正确的:字符串的全部开销就是存储这32个字节和常量字符串空间的开销:这些函数仅在编译时使用,并且在插入后也可能被内联和抛弃对象的创建和使用(就像您正在使用结构,并且仅在没有任何函数调用的情况下直接引用它一样,请确保存在重复的调用而不是函数跳转,但这通常更快并且使用更少的空间)。本质上,每当您调用任何函数时,编译器都将用该指令替换该调用以按实际方式进行操作,但语言设计人员已设置了例外情况。

简介:C ++对象不知道它们是什么。与它们接口的所有工具都是静态内联的,并且在运行时会丢失。这使得使用类和使用数据填充结构一样高效,并且直接使用该数据而无需调用任何函数(这些函数是内联的)。

这与COM / ObjectiveC和javascript的方法完全不同,后者会动态保留类型信息,但会浪费运行时开销,内存管理和构造调用,因为编译器无法丢弃此信息:这是必要的用于动态调度。反过来,这使我们能够在运行时与程序“对话”,并通过具有可反射的组件在运行时对其进行开发。


2
抱歉,但是此“答案”的某些部分有误或存在危险的误导。可悲的是,注释空间太小而无法全部列出(大多数方法不会内联,这将阻止虚拟调度并使二进制文件膨胀;即使内联,在某个可访问的地方也可能有可寻址的副本;无关紧要的代码示例最坏的情况会破坏您的堆栈,而在最好的情况下则不符合您的假设; ...)
hoffmale16年

答案很幼稚,我只想表达一下为什么不能引用构造函数/析构函数的猜测。我同意在虚拟类的情况下,vtable必须持久化,并且可寻址代码必须在内存中,以便vtable可以引用它。但是,未实现虚拟类的类似乎是内联的,如std :: string一样。并非所有内容都内联,但是似乎没有被最小化地放入内存中某个“匿名”代码块中的内容。另外,代码如何破坏堆栈?当然,我们丢失了字符串,但是否则,我们要做的就是重新解释。
德米特里

当无意中修改了存储位置的内容时,在计算机程序中会发生内存损坏。该程序有意执行此操作,不再尝试使用该字符串,因此不会造成损坏,只是浪费了堆栈空间。但是,是的,不再保留字符串的不变式,它使范围变得混乱(在结束时,堆栈得以恢复)。
德米特里

根据字符串的实现,您可能会覆盖不需要的字节。如果字符串类似于struct { int size; const char * data; };(就像您似乎假设的那样),则在一个内存地址上写入4 * 4字节= 16个字节,而在x86机器上仅保留8个字节,因此8个字节被覆盖在其他数据上(这可能会破坏堆栈) )。幸运的是,std::string通常对短字符串进行一些就地优化,因此在使用一些主要的std实现时,对于示例来说应该足够大。
hoffmale

@hoffmale,您绝对正确,它可以是4个字节,也可以是8个字节,甚至1个字节。但是,一旦知道了字符串的大小,便会知道该内存在当前作用域中处于堆栈中,可以随意使用它。我的观点是,如果您确实知道结构,则该结构将以独立于有关类的任何信息的方式打包,这与COM对象不同,COM对象具有一个将其类标识为IUnknown vtable一部分的uuid。反过来,编译器直接通过内联或损坏的静态函数访问此数据。
德米特里
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.