使用非虚拟析构函数扩展基类是否危险?

Aku*_*ete 12 c++ polymorphism memory-management

在以下代码中:

class A {
};
class B : public A {
};
class C : public A {
   int x;
};

int main (int argc, char** argv) {
   A* b = new B();
   A* c = new C();

   //in both cases, only ~A() is called, not ~B() or ~C()
   delete b; //is this ok?
   delete c; //does this line leak memory?

   return 0;
}
Run Code Online (Sandbox Code Playgroud)

当使用具有成员函数的非虚析构函数(如C类)调用类上的delete时,内存分配器可以告诉对象的正确大小是什么吗?如果没有,记忆是否泄露?

其次,如果类没有成员函数,并且没有明确的析构函数行为(比如B类),那么一切都好吗?

我问这个是因为我想创建一个扩展的类std::string,(我知道不推荐,但为了讨论它只是承担它),并重载+=,+运算符.-Weffc ++给了我一个警告,因为它std::string有一个非虚拟析构函数,但是如果子类没有成员并且不需要在它的析构函数中做任何事情,这是否重要?

FYI +=重载是进行正确的文件路径格式化,因此可以使用路径类,如:

class path : public std::string {
    //... overload, +=, +
    //... add last_path_component, remove_path_component, ext, etc...
};

path foo = "/some/file/path";
foo = foo + "filename.txt";
std::string s = foo; //easy assignment to std::string
some_function_taking_std_string (foo); //easy implicit conversion
//and so on...
Run Code Online (Sandbox Code Playgroud)

我只是想确保有人这样做:

path* foo = new path();
std::string* bar = foo;
delete bar;
Run Code Online (Sandbox Code Playgroud)

不会导致内存分配问题?

GMa*_*ckG 13

不,从没有虚拟析构函数的类公开继承是不安全的,因为如果通过基类删除派生,则输入未定义的行为.派生类的定义是无关紧要的(数据成员与否等):

§5.3.5/ 3:在第一个备选(删除对象)中,如果操作数的静态类型与其动态类型不同,则静态类型应为操作数的动态类型的基类,静态类型应具有虚析构函数或行为未定义. (强调我的.)

代码中的这两个示例都会导致未定义的行为.您可以非公开地继承,但这显然会破坏使用该类然后扩展它的目的.(因为不再可能通过基指针删除它.)

这是(一个原因*)为什么你不应该继承标准库类.将最好的解决办法是将它与自由的功能扩展.事实上,即使你可以,你也应该更喜欢自由功能.


*另一个原因是:你真的想用新的字符串类替换所有的字符串用法,只是为了获得一些功能吗?这是很多不必要的工作.


Mic*_*son 5

所以每个人都说你不能这样做 - 它会导致不确定的行为.但是在某些情况下它是安全的.如果您从不动态创建类的实例,那么您应该没问题.(即没有新的电话)

也就是说,这通常被认为是一件坏事,因为有人可能会尝试在以后某个时候创建​​一个多态.(你可能可以通过私有的未实现的运算符来防止这种情况,但我不确定.)

我有两个例子,我不讨厌从具有非虚拟析构函数的类派生.第一个是使用临时创造语法糖...这是一个人为的例子.

class MyList : public std::vector<int>
{
   public:
     MyList operator<<(int i) const
     {
       MyList retval(*this);
       retval.push_back(i);
       return retval;
     }
   private: 
     // Prevent heap allocation
     void * operator new   (size_t);
     void * operator new[] (size_t);
     void   operator delete   (void *);
     void   operator delete[] (void*);
};

void do_somthing_with_a_vec( std::vector<int> v );
void do_somthing_with_a_const_vec_ref( const std::vector<int> &v );

int main()
{
   // I think this slices correctly .. 
   // if it doesn't compile you might need to add a 
   // conversion operator to MyList
   std::vector<int> v = MyList()<<1<<2<<3<<4;

  // This will slice to a vector correctly.
   do_something_with_a_vec( MyList()<<1<<2<<3<<4 );

  // This will pass a const ref - which will be OK too.
   do_something_with_a_const_vec_ref( MyList()<<1<<2<<3<<4 );

  //This will not compile as MyList::operator new is private
  MyList * ptr = new MyList();
}
Run Code Online (Sandbox Code Playgroud)

我能想到的另一个有效用法来自于C++中缺少模板typedef.这是你如何使用它.

// Assume this is in code we cant control
template<typename T1, typename T2 >
class ComplicatedClass
{
  ...
};

// Now in our code we want TrivialClass = ComplicatedClass<int,int>
// Normal typedef is OK
typedef ComplicatedClass<int,int> TrivialClass;

// Next we want to be able to do SimpleClass<T> = ComplicatedClass<T,T> 
// But this doesn't compile
template<typename T>
typedef CompilicatedClass<T,T> SimpleClass;

// So instead we can do this - 
// so long as it is not used polymorphically if 
// ComplicatedClass doesn't have a virtual destructor we are OK.
template<typename T>
class SimpleClass : public ComplicatedClass<T,T>
{
  // Need to add the constructors we want here :(
  // ...
   private: 
     // Prevent heap allocation
     void * operator new   (size_t);
     void * operator new[] (size_t);
     void   operator delete   (void *);
     void   operator delete[] (void*);
}
Run Code Online (Sandbox Code Playgroud)

下面是一个更具体的例子.您希望将std :: map与自定义分配器一起用于许多不同类型,但您不希望不可维护

std::map<K,V, std::less<K>, MyAlloc<K,V> >
Run Code Online (Sandbox Code Playgroud)

通过你的代码乱七八糟.

template<typename K, typename V>
class CustomAllocMap : public std::map< K,V, std::less<K>, MyAlloc<K,V> >
{
  ...
   private: 
     // Prevent heap allocation
     void * operator new   (size_t);
     void * operator new[] (size_t);
     void   operator delete   (void *);
     void   operator delete[] (void*);
}; 

MyCustomAllocMap<K,V> map;
Run Code Online (Sandbox Code Playgroud)