zoukankan      html  css  js  c++  java
  • C++构造函数、new、delete

    1. c++在调用构造函数时,才会把最开始的虚表指针指向虚表。

    2.在构造函数或者析构函数中调用虚函数。

        编译上没有问题。

        运行时,调用虚函数不会发生多态行为,会调用正在构造的类的虚函数。

      详细可见c++中的说明:

    Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2).
    When a virtual function is called directly or indirectly from a constructor (including the mem-initializer or
    brace-or-equal-initializer for a non-static data member) or from a destructor, and the object to which the
    call applies is the object under construction or destruction, the function called is the one defined in the
    constructor or destructor’s own class or in one of its bases, but not a function overriding it in a class derived
    from the constructor or destructor’s class, or overriding it in one of the other base classes of the most derived
    object (1.8). If the virtual function call uses an explicit class member access (5.2.5) and the object-expression
    refers to the object under construction or destruction but its type is neither the constructor or destructor’s
    own class or one of its bases, the result of the call is undefined.

    //大意就是上面提到的运行时,因为子类没有构造好(指向虚表的指针都还没有初始化),所以没有多态行为。但如果要显示在父类中调用子类的虚函数。那么会出现undefined行为。

    3.new、operator new与placement new

    new operator/delete operator就是new和delete操作符,而operator new/operator delete是函数。

    new operator
    1)  调用operator new分配内存 ;
    2)  调用构造函数生成类对象;
    3)  返回相应指针。

    operator new
    (1)只分配所要求的空间,不调用相关对象的构造函数。当无法满足所要求分配的空间时,则
            ->如果有new_handler,则调用new_handler,否则
            ->如果没要求不抛出异常(以nothrow参数表达),则执行bad_alloc异常,否则
            ->返回0
    (2)可以被重载
    (3)重载时,返回类型必须声明为void*
    (4)重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t
    (5)重载时,可以带其它参数

    //我的理解:new操作符(new operator)做的事情是不变的,首先会调用operator new,如果你的类重载了operator new,则会调用你重载的。没有重载的话,则调用::operator new

    #include <iostream>
    #include <stdlib.h>
    using namespace std;
    
    class A {
    
    public:
        A() {
            cout << "A construct" << endl;
            c = 1;//程序会崩,因为this为NULL
        }
    
        void * operator new(unsigned int sz) {
            cout << "operator new" << endl;
            return NULL;
        }
        int c;
    
    };
    
    int main()
    {
        A *a = new A;
        return 0;
    }

    //从上面的代码可以看出,new A时,先调用了operator new, 然后再调用构造函数,因为operator new 返回的是NULL,所以执行到c = 1时,程序直接崩了。正好了证明了new操作符做的事情。

    同理delete,new [], delete[]都可以重载相应的运算符。

    placement new 

    placement new 是重载operator new 的一个标准、全局的版本,它不能够被自定义的版本代替(不像普通版本的operator new和operator delete能够被替换)。也就是无需、也不能重载。

    void *operator new( size_t, void * p ) throw() { return p; } 

    //注意 throw()
    它是函数提供者和使用者的一种君子协定,标明该函数不抛出任何异常。 之所以说是君子协定,是因为实际上内部实现是需要人肉确保。  如果一个标明throw()的函数内部发生了throw: 
    1,如果内部直接throw something,编译器会发现并指出; 
    2. 如果是内部调用了一个可能throw something的函数,编译器无法发现,运行时一旦这个内部的函数throw,程序会abort。 
    这是异常规范,只会出现在声明函数中,表示这个函数可能抛出任何类型的异常,例如: 
    void GetTag() throw(int);表示只抛出int类型异常 
    void GetTag() throw(intchar);表示抛出in,char类型异常 void GetTag() throw();表示不会抛出任何类型异常 void GetTag() throw(...);表示抛出任何类型异常 void GetTag() throw(int);表示只抛出int类型异常 
    并不表示一定会抛出异常,但是一旦抛出异常只会抛出int类型,如果抛出非 int类型异常,调用unexsetpion()函数,退出程序。 
    假如你加一个throw()属性到你的永远不会抛出异常的函数中,编译器会非常聪明的知道代码的意图和决定优化方式

    placement new的执行忽略了size_t参数,只返还第二个参数。其结果是允许用户把一个对象放到一个特定的地方,达到调用构造函数的效果。和其他普通的new不同的是,它在括号里多了另外一个参数。比如:

    Widget * p = new Widget;                    //ordinary new

    pi = new (ptr) int;    //placement new

    括号里的参数ptr是一个指针,它指向一个内存缓冲器,placement new将在这个缓冲器上分配一个对象。Placement new的返回值是这个被构造对象的地址(比如括号中的传递参数)。

    stl中用到了这个技巧。

    比如:uninitalized_copy() 函数,就是依次拷贝数据时,调用拷贝构造函数,而不会调用赋值函数。

    拷贝很多数据的时候,如果要调用赋值函数,那么就得new一个出来。然后再赋值,这无疑增加无用的开销。

    最好的方法当然是,先申请整块内存空间,然后把数据拷贝过来。这个时候就需要用到placement new了。

    在申请好的内存空间上调用拷贝构造函数。

    stl源码剖析51页:

    template <class T1, class T2>
    inline void construct(T1*p, const T2& value) {
          new (p) T1(value);//p调用T1的拷贝构造函数          
    }

    4、拷贝(复制)构造函数为什么不能用值传

    会产生死循环,因为如果值传递的话,调用拷贝构造函数的时候,传参时又会调用拷贝构造函数。导致死循环。

    5、构造函数/析构函数抛出异常的问题

    构造函数抛出异常:
        1.不建议在构造函数中抛出异常;
        2.构造函数抛出异常时,析构函数将不会被执行;
    C++仅仅能删除被完全构造的对象(fully contructed objects),只有一个对象的构造函数完全运行完毕,这个对象才能被完全地构造。对象中的每个数据成员应该清理自己,如果构造函数抛出异常,对象的析构函数将不会运行。如果你的对象需要撤销一些已经做了的动作(如分配了内存,打开了一个文件,或者锁定了某个信号量),这些需要被撤销的动作必须被对象内部的一个数据成员记住处理。

    析构函数抛出异常:
        在有两种情况下会调用析构函数。第一种是在正常情况下删除一个对象,例如对象超出了作用域或被显式地delete。第二种是异常传递的堆栈辗转开解(stack-unwinding)过程中,由异常处理系统删除一个对象。
    在上述两种情况下,调用析构函数时异常可能处于激活状态也可能没有处于激活状态。遗憾的是没有办法在析构函数内部区分出这两种情况。因此在写析构函数时你必须保守地假设有异常被激活,因为如果在一个异常被激活的同时,析构函数也抛出异常,并导致程序控制权转移到析构函数外,C++将调用terminate函数。这个函数的作用正如其名字所表示的:它终止你程序的运行,而且是立即终止,甚至连局部对象都没有被释放。
    概括如下:
        1.析构函数不应该抛出异常;
        2.当析构函数中会有一些可能发生异常时,那么就必须要把这种可能发生的异常完全封装在析构函数内部,决不能让它抛出函数之外;
        3.当处理另一个异常过程中,不要从析构函数抛出异常;

        在构造函数和析构函数中防止资源泄漏的好方法就是使用smart point(智能指针),C++ STL提供了类模板auto_ptr,用auto_ptr对象代替原始指针,你将不再为堆对象不能被删除而担心,即使在抛出异常时,对象也能被及时删除。因为auto_ptr的析构函数使用的是单对象形式的delete,而不是delete [],所以auto_ptr不能用于指向对象数组的指针。当复制 auto_ptr 对象或者将它的值赋给其他 auto_ptr 对象的时候,将基础对象的所有权从原来的 auto_ptr 对象转给副本,原来的 auto_ptr 对象重置为未绑定状态。因此,不能将 auto_ptrs 存储在标准库容器类型中。如果要将智能指针作为STL容器的元素,可以使用Boost库里的shared_ptr。

  • 相关阅读:
    文件上传以及JS链式结构
    JQUERY事件
    Asp.Net MVC及Web API框架配置会碰到的几个问题及解决方案(转)
    WebSocket
    用户态和核心态的区别
    Visual Studio中的.suo(Solution User Options)文件
    探索Visual Studio生成的.vs文件夹内部结构和作用
    VMware搭建虚拟机服务器
    【图解】用虚拟机做服务器
    .NET C# 创建WebService服务简单的例子
  • 原文地址:https://www.cnblogs.com/chenhuan001/p/7452478.html
Copyright © 2011-2022 走看看