1、设计一个类,不能被继承:
我们知道派生类在构造对象时,会先调用其基类的构造函数,然后再调用派生类的构造函数。所以,如果我们把基类的构造函数和析构函数设计为私有的,那么派生类就不能调用基类的构造函数了,自然也就不能继承了。但是这样的话,这个基类也不能实例化了。我们可以想到通过静态方法,通过一个静态方法来返回类的实例,另一个静态方法来释放该对象。代码如下:
////将基类的构造函数和析构函数设为私有的( static方法可以解决实例化问题)
//基类不能被继承,同时也解决了实例化问题,但只能是在堆上分配内存
class AA
{
private:
AA() { cout << "AA()" << endl; }
~AA() { cout << "AA()" << endl; }
public:
static void test(AA* a)
{
cout << "AA::test()" << endl;
}
static AA* construct(int n)
{
AA *a = new AA();
a->_a = n;
return a;
}
static void destroy(AA *aa)
{
delete aa;
}
private:
int _a;
};
class BB :public AA
{
};
void test()
{
//BB b;
AA *a = AA::construct(3);
AA::test(a);
AA::destroy(a);
}
C++11中的final关键字:
//C++11里面的final关键字
class Base //final(修饰类时,该类不能被继承)
{
public:
virtual void fun()
{
cout << "Base::fun()" << endl;
}
};
class Derived :public Base
{
public:
void fun()
{
cout << "Base::fun()" << endl;
}
};
有什么办法可以让类既不能被继承,又可以在栈上和堆上都能创建对象呢?不妨考虑一下友元
- 先创建一个基类A,将其构造函数和析构函数都声明为私有的;
- 我们要求的不能被继承的类B声明为A的友元类,这样B可以访问A类的构造函数和析构函数,B可以正常构造;
- 同时还需要让类B虚拟继承A类,此时B类的子类C在构造对象时,会直接调用A类的构造函数,但是由于友元关系是不能被继承的,所以,C类调用A类的构造函数会报错,也就是说C类不能成功构造出对象,所以,B类是不可以被继承的。代码如下:
//类不能被继承,但可以在栈上和堆上创建对象:(友元)
//1、先创建一个基类A,将其构造和析构函数都声明为私有的;
//2、将不能被继承的类B声明为A的友元类,这样B可以访问A类的构造析构函数,B可以正常构造;
//3、同时还需要类B虚拟继承A类,此时B类的子类C在构造对象时,会直接调用A类的构造函数,但是
//友元关系是不能被继承的,所以C类不能成功的构造出对象,所以B类是不可以被继承的。
template<class T>
class A
{
friend T;
private:
int a;
A()
{
cout << "A()" << endl;
}
};
class B :public virtual A<B>
{
public:
B()
{
cout<<"B()" << endl;
}
};
class C :public B
{
public:
C() //友元不会继承,编译出错
{
cout << "C()" << endl;
}
};
int main()
{
B b;
C c;
return 0;
}
这里需要说明的是:我们设计的不能被继承的类B对基类A的继承必须是虚继承,这样一来C类继承B类时会去直接调用A的构造函数,而不是像普通继承那样,先调用B的构造函数再调用A的构造函数;C类直接调用A类的构造函数,由于A类的构造函数是私有的,而B是A的友元,C类不是A的友元,友元关系不会继承,因此会编译报错。除此之外,在C++11中已经像Java一样有了final关键字,被final修饰的虚函数(**只能是虚函数**)不能被重载,被final修饰的类不能被继承。
2、设计一个类,只能在栈上创建对象?
使用new运算符,对象会建立在堆上,也就是说只要不用new去创建对象就可以实现,我们知道new和delete分别调用了operator new和operator delete,如果我们把这两个函数声明为私有的,操作符new就不能用了。
////设计一个类,只能在栈上创建对象?
//不用new去创建对象,将operator new和operator delete设为私有的。操作符new就不能用了。
class A
{
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
void *operator new(size_t size){};
void operator delete(void *ptr) {};
};
int main()
{
A a;
return 0;
}
3、设计一个类,只能在堆上创建?
1. 使用new运算符,对象就可以在堆上建立。如果我们将构造函数和析构函数定义为protected(可以让类被继承),然后定义两个公有的静态函数调用new和delete,来创建和销毁对象。
2.将析构函数声明为私有的。 对象建立在栈上面时,是由编译器分配空间的,调用构造函数来构造对象,编译器释放对象,编译器管理了对象的整个生命周期,编译器为对象分配空间的时候,只要是非静态的函数都会检查,包括析构函数,如果析构函数不可访问,编译器就无法调用类的析构函数来释放内存,那么编译器将无法在栈上为对象分配内存。
//设计一个类,只能在堆上创建对象?
//方法一 使用new运算符,对象就可以在堆上创建
class A
{
protected:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
public:
static A* _Construct()
{
return new A();
}
static void Destroy(A *p)
{
delete p;
p = NULL;
}
};
int main()
{
A* p = A::_Construct()
;
A::Destroy(p);
}
//方法二 将析构函数声明为私有的
class A
{
public:
A()
{
cout << "A()" << endl;
}
void Destroy()
{
delete this;
}
private:
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
A* a = new A();
(*a).Destroy();
}
4、设计一个类,只能有一个实例?
- 为了防止从类的外部调用构造函数,产生类的新的实例,我们应该把类的构造函数声明为protected或private
- 由于只能生成一个类的实例,我们考虑用静态函数来记录,到底之前有没有构造过类的实例
- 如果没有构造过,那么就构造一个新的实例,如果构造过,那么就将之前创建的实例返回。
- 为了保证之前构造的实例,在程序运行期间一直存在,不被析构,我们只能把指向这个实例的指针声明成静态变量,存放在静态区,把这个类的实例用new来构造,并放在堆中。
单例模式的特点:
1、一个类只能有一个实例。
2、一个类必须自己创建自己的唯一实例。
3、一个类必须给所有其他对象提供这一实例。
懒汉式单例模式:在类加载时,不创建实例,因此类的加载速度快,但运行时获取对象的速度慢。第一次使用的时候加载:
//懒汉单例模式(类的加载速度快,运行时获取对象慢)
class singleton
{
private:
singleton()
{
cout << "singletion()" << endl;
}
singleton(const singleton &s);
singleton& operator = (const singleton& s);
public:
static singleton* getInstance()
{
if (_instance == NULL)
_instance = new singleton();
return _instance;
}
static void Release()
{
if (NULL != _instance)
{
delete _instance;
_instance = NULL;
}
}
private:
static singleton* _instance;
};
singleton* singleton::_instance = NULL;
int main()
{
singleton* p = singleton::getInstance();
return 0;
}
相关问题: 懒汉模式是否线程安全?
- 不安全,第一次调用时new,假如两个线程都是第一次调用,可能new了两次
>如何改成线程安全的?
- 只有第一次调用才会有线程不安全的问题,if前后加锁,后面的线程被挂起等待,效率问题。
- 加锁的外面在加一层if,不用再加锁解锁了;保证了线程安全和效率问题。
- volatile修饰inst,如果没有从内存读数据,而是从寄存器读取数据,还是有问题。
饿汉单例模式:在类加载时完成了初始化,所以类加载比较慢,但获取对象的速度快。模块加载的时候初始化,影响程序启动时间:
//饿汉单例模式(类加载比较慢,但获取对象的速度快)
class singleton
{
private:
singleton()
{
cout << "singletion()" << endl;
}
public:
static singleton* getInstance()//不用同步(类加载时已经初始化,不会有多线程的问题)
{
return &_instance;
}
private:
static singleton _instance;
};
singleton* singleton::_instance = new singleton();
int main()
{
singleton* p = singleton::getInstance();
return 0;
}
template<class T>
class S
{
static T* Instance()
{
return &inst;
}
static T inst;
};
T* ptr = S::Instance();
懒汉与饿汉模式的选择:
- 由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
- 在访问量较小时,采用懒汉实现。这是以时间换空间。
单例模式的适用场景
- 系统只需要一个实例对象,或者考虑到资源消耗的太大而只允许创建一个对象。
- 客户调用类的单个实例只允许使用一个公共访问点,除该访问点外不允许通过其它方式访问该实例 (就是共有的静态方法)。