Interesting C/C++ interview questions

User avatar
шпиён
Уже с Приветом
Posts: 3459
Joined: 29 Oct 2002 20:08
Location: US

Post by шпиён »

Vovka wrote:Я не предлагаю чего-то убирать из языка, я предлагаю - если делаете невиртуальный деструктор в базовом классе, то лучше его сделать protected.


В том-то и дело, что исходно вопрос был про дестрактор в небазовом законченном классе, предназначенным для эксплуатации as is, без наследования. protected destructor сделает такой класс непригодным к эксплуатации по основному пути.
Big Cheese
Уже с Приветом
Posts: 1211
Joined: 02 Jul 2000 09:01
Location: SFBA

Post by Big Cheese »

Yuri_p33 wrote:
ballymahon wrote:
8K wrote:Неужели нельзя "запечатать" класс собственными средствами С++? Не верю.
А как интересно? Можно конечно не использовать виртуальных функций и protected членов (о чем уже упоминалось) и тогда от наследования не будет особого толку, хотя сама возможность и будет присутствовать.
Как запретить наследование от данного класса - разве что сделать все конструкторы приватными и создавать обьекты статической функцией?
Примерно так:

Code: Select all

class Final
{
protected:
   Final(int) {}
};

class MySealedClass: private virtual Final
{
public:
   MySealedClass(): Final(0) {};
   /* your code */
};

class MyDerivedClass: public MySealedClass
{
};

Хотя этот код скомпилируется, при попытке создать переменную типа MyDerivedClass компилятор пошлет нас подальше. Говорят, такой вопрос даже на интервью бывает :)

После добавления явного вызова Final(0) в конструктор MyDerivedClass все компилируется и работает (VC++):

Code: Select all

class Final 
{
protected:
   Final(int) {}
};

class MySealedClass: private virtual Final
{
public:
   MySealedClass(): Final(0) {};
   /* your code */
};

class MyDerivedClass: public MySealedClass
{
public:
   MyDerivedClass() : Final(0) {}
};

int main(int argc, char* argv[])
{
   MySealedClass cc;
   MyDerivedClass c;
   return 0;
}
8K
Уже с Приветом
Posts: 5552
Joined: 20 Mar 2001 10:01
Location: SFBA

Post by 8K »

А зачем virtual? Мемберов же нет кроме конструктора.
---------------
Похоже, глупость написал. Пойду Страуструпа читать.
Last edited by 8K on 07 Feb 2003 18:30, edited 1 time in total.
Увидев друга, Портос вскрикнул от радости...
Yuri_p33
Уже с Приветом
Posts: 394
Joined: 12 Feb 2001 10:01
Location: USA

Post by Yuri_p33 »

Big Cheese wrote:После добавления явного вызова Final(0) в конструктор MyDerivedClass все компилируется и работает (VC++)
Да, есть такая бяка :(
Попробуем по другому:

Code: Select all

class Final 
{
private:
   Final(int) {}
friend class MySealedClass;
};

class MySealedClass: private virtual Final
{
public:
   MySealedClass(): Final(0) {};
   /* your code */
};

class MyDerivedClass: public MySealedClass
{
public:
   MyDerivedClass(): Final(0) {}; //Error - can't access private member
};

int _tmain(int argc, _TCHAR* argv[])
{
   MySealedClass s;
   MyDerivedClass a;
   return 0;
}
Только кривовато это :(
Мнения?
Yuri_p33
Уже с Приветом
Posts: 394
Joined: 12 Feb 2001 10:01
Location: USA

Post by Yuri_p33 »

8K wrote:А зачем virtual? Мемберов же нет кроме конструктора.
Что бы изменить порядок вызовов конструкторов.
vovka777
Новичок
Posts: 23
Joined: 06 May 2001 09:01

Post by vovka777 »

Вот что пишет об этой проблеме сам Страуструп:
http://www.research.att.com/~bs/bs_faq2 ... derivation

Code: Select all

    class Usable;

    class Usable_lock {
        friend class Usable;
    private:
        Usable_lock() {}
    };

    class Usable : public virtual Usable_lock {
        // ...
    public:
        Usable();
        Usable(char*);
        // ...
    };

    Usable a;

    class DD : public Usable { };

    DD dd;  // error: DD::DD() cannot access
            // Usable_lock::Usable_lock(): private  member
Yuri_p33
Уже с Приветом
Posts: 394
Joined: 12 Feb 2001 10:01
Location: USA

Post by Yuri_p33 »

vovka777 wrote:Вот что пишет об этой проблеме сам Страуструп:
http://www.research.att.com/~bs/bs_faq2 ... derivation
"Какой удар со стороны классика!" (с)О.Бендер :mrgreen:
User avatar
шпиён
Уже с Приветом
Posts: 3459
Joined: 29 Oct 2002 20:08
Location: US

Post by шпиён »

Короче, вот решение исходной проблемы ("оставить дестрактор базового класса невиртуальным, но предотвратить ошибочное в таком случае удаление наследника через указатель на базовый"):

Code: Select all

#include <iostream>

using namespace std;

class Base
{
protected:
   int a;
public:
   Base(int a_):a(a_){}
   ~Base()
   {
      cout << "destructing object of Base with a=" << a << endl;
   }
private: //uncomment this line to protect from the incorrect usage
   void operator delete(void * p)
   {
      cout << "deleting object of Base with a="
         << ((Base*)p)->a //do not do this in producion code!
         << endl;
      ::operator delete(p);
   }
public:   //if you still need to create Base on the heap, do this
   static Base * create(int a_){return new Base(a_);}
   static void destroy(Base * p){delete p;}
};

class Derived : public Base
{
public:
   Derived(int a_):Base(a_){}
   ~Derived()
   {
      cout << "destructing object of Derived with a=" << a << endl;
   }
   
   void operator delete(void * p)   //need to enable deleting again
   {
      cout << "deleting object of Derived with a="
         << ((Derived*)p)->a //do not do this in producion code!
         << endl;
      ::operator delete(p);
   }
   
};

void main()
{
   Base bc(1);

//   Base * pb = new Base(2);
//   delete pb;

   Base * pb1 = Base::create(3);
   Base::destroy(pb1);

   Derived * pd = new Derived(4);
   delete pd;

   Base * pbd = new Derived(5);
   delete pbd;//unfortunately this compiles on VC6
}


К сожалению, из-за ошибки в VC6 с правами доступа на этом компиляторе метод не работает.
interrupt
Уже с Приветом
Posts: 4022
Joined: 08 Dec 1999 10:01
Location: USA <-> Moscow

Post by interrupt »

шпиён wrote:
interrupt wrote:Хм, я же вроде прописал там:

Code: Select all

virtual ~MyInterface() = 0 {   }


если вы это имели в виду?


Сорри, скобочек не заметил.
Так в чем пойнт =0 тогда?


Потому что технически, если не поставить =0, то класс уже как бы не будет абстрактным. Например, у нас есть код:

Code: Select all

class IMyInterface
{
public:
   virtual ~IMyInterface() = 0;
};


Это не есть хорошо, потому что мы заставляем класс, который будет наследовать от этого интерфейса, явно задавать деструктор.

C другой стороны вот такой код:

Code: Select all

class IMyInterface
{
public:
   virtual ~IMyInterface() = 0
                { }
};


и класс IMyInterface держит абстрактным (потому что вы не можете создать экземпляр этого класса), с другой стороны и его потомку не обязательно задавать деструктор.
Т.е. вот этот класс уже не является абстракным:

Code: Select all

class MyImpl : IMyInterface
{
};
Grizly Bear
Posts: 1
Joined: 05 Feb 2003 20:41
Location: Brooklyn, NY

Post by Grizly Bear »

Yuri_p33 wrote:
ballymahon wrote:
8K wrote:Неужели нельзя "запечатать" класс собственными средствами С++? Не верю.
А как интересно? Можно конечно не использовать виртуальных функций и protected членов (о чем уже упоминалось) и тогда от наследования не будет особого толку, хотя сама возможность и будет присутствовать.
Как запретить наследование от данного класса - разве что сделать все конструкторы приватными и создавать обьекты статической функцией?
Примерно так:

Code: Select all

class Final
{
protected:
   Final(int) {}
};

class MySealedClass: private virtual Final
{
public:
   MySealedClass(): Final(0) {};
   /* your code */
};

class MyDerivedClass: public MySealedClass
{
};

Хотя этот код скомпилируется, при попытке создать переменную типа MyDerivedClass компилятор пошлет нас подальше. Говорят, такой вопрос даже на интервью бывает :)


This solution is very clever, but, if a developer who really wants to derive from MySealedClass can always change MyDerivedClass so that the Final constructor is called explicitely, e.g.:

Code: Select all

class MyDerivedClass: public MySealedClass
{
public:
   MyDerivedClass(): Final(0) {};
   /* your code */
};


I think the following solution is a little bit better, because MySealedClass can only be derived from if the declaration of Final is changed. This prompts developer to think twice, especially if he/she did not write the MySealedClass class or does not control the source.

Code: Select all

class Final
{
private:
   friend class MySealedClass;
   Final() {}
};

class MySealedClass: private virtual Final
{
public:
   MySealedClass() {};
   /* your code */
};

class MyDerivedClass : public MySealedClass
{
public:
   MyDerivedClass () {} //error here
   /* your code */
};

int main(int argc, char* argv[])
{
    MySealedClass a;

    return 0;
}


In this case, the compilation of MyDerivedClass class fails (at least in Visual C++ v6) because MyDerivedClass can not access private constructor of Final.
Also, compilation fails even when no instances of MyDerivedClass are created.
User avatar
шпиён
Уже с Приветом
Posts: 3459
Joined: 29 Oct 2002 20:08
Location: US

Post by шпиён »

interrupt wrote:
шпиён wrote:
interrupt wrote:Хм, я же вроде прописал там:

Code: Select all

virtual ~MyInterface() = 0 {   }


если вы это имели в виду?


Сорри, скобочек не заметил.
Так в чем пойнт =0 тогда?


Потому что технически, если не поставить =0, то класс уже как бы не будет абстрактным.



Не-не-не-не-не. :) Так не пойдет. :) Если уж ты объявляешь интерфейс, то там хватает чистых виртуальных функций (т.е. есть как минимум одна - иначе нет смысла в интерфейсе) помимо дестрактора, чтобы сделать интерфейсный класс абстрактным. :)
Отмазка не принята. И заставлять наследников определять дестракторы (даже пустые) тоже не хорошо - имплементация - это их дело!
:gen1:
interrupt
Уже с Приветом
Posts: 4022
Joined: 08 Dec 1999 10:01
Location: USA <-> Moscow

Post by interrupt »

шпиён wrote:Не-не-не-не-не. :) Так не пойдет. :) Если уж ты объявляешь интерфейс, то там хватает чистых виртуальных функций (т.е. есть как минимум одна - иначе нет смысла в интерфейсе) помимо дестрактора, чтобы сделать интерфейсный класс абстрактным. :)
Отмазка не принята. И заставлять наследников определять дестракторы (даже пустые) тоже не хорошо - имплементация - это их дело!
:gen1:


В том то и дело, что наоборот, наследники могут не обьявлять деструкторы (именно потому что это их дело). Т.е. если бы мы в интерфейсе написали бы что-то типа:
virtual ~IMyInterface() = 0;
то именно эта конструкция требует, чтобы наследник обьявил деструктор. Я же написал в интерфейсе:
virtual ~IMyInterface() = 0 { }
Такая конструкция разрешает наследнику явно не обьявлять деструктор (он уже есть) и с другой стороны, показывает что класс IMyInterface - "чистый" интерфейс, потому что его невозможно создать т.к. указанно = 0 в обьявлении деструктора.
Рекомендации ANSI/ISO кстати :)
z1
Posts: 7
Joined: 08 Feb 2003 22:07

Post by z1 »

Yuri_p33 wrote:
Big Cheese wrote:После добавления явного вызова Final(0) в конструктор MyDerivedClass все компилируется и работает (VC++)
Да, есть такая бяка :(
Попробуем по другому:


Можно еще так:

Code: Select all

class MySealedClass
{
public:
   static MySealedClass * CreateInstance();
private:
   MySealedClass() {}
};

MySealedClass * MySealedClass::CreateInstance()
{
  return new MySealedClass();
}

class MyDerivedClass: public MySealedClass
{
public:
  MyDerivedClass() {} //Error - can't access private member
};
User avatar
chris
Уже с Приветом
Posts: 1688
Joined: 25 Oct 2001 09:01
Location: Boston, MA

Post by chris »

Yuri_p33 wrote:
Big Cheese wrote:После добавления явного вызова Final(0) в конструктор MyDerivedClass все компилируется и работает (VC++)
Да, есть такая бяка :(
Попробуем по другому:

Code: Select all

class Final 
{
private:
   Final(int) {}
friend class MySealedClass;
};

class MySealedClass: private virtual Final
{
public:
   MySealedClass(): Final(0) {};
   /* your code */
};

class MyDerivedClass: public MySealedClass
{
public:
   MyDerivedClass(): Final(0) {}; //Error - can't access private member
};

int _tmain(int argc, _TCHAR* argv[])
{
   MySealedClass s;
   MyDerivedClass a;
   return 0;
}
Только кривовато это :(
Мнения?


а почему бы просто не использовать конструкторы по умолчанию:

class Final
{
protected:
Final() {};
};

class MySealedClass: public Final
{
public:
MySealedClass() {};
};

class MyDerivedClass: public MySealedClass
{
MyDerivedClass () {};
};

int main()
{
MyDerivedClass a;
}

Visual.Net ругаеться: error C2248: 'MyDerivedClass::MyDerivedClass' : cannot access private member declared in class 'MyDerivedClass'

вроде то, что и хотели ?
Yuri_p33
Уже с Приветом
Posts: 394
Joined: 12 Feb 2001 10:01
Location: USA

Post by Yuri_p33 »

chris wrote:...Visual.Net ругаеться: error C2248: 'MyDerivedClass::MyDerivedClass' : cannot access private member declared in class 'MyDerivedClass'
вроде то, что и хотели ?
У вас конструктор MyDerivedClass объявлен как private. Поэтому и ругается.

Return to “Работа и Карьера в IT”