Interesting C/C++ interview questions

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. Поэтому и ругается.
Big Cheese
Уже с Приветом
Posts: 1211
Joined: 02 Jul 2000 09:01
Location: SFBA

Post by Big Cheese »

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

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

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

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

int main()
{
MyDerivedClass a;
}

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

вроде то, что и хотели ?


ругается он на то, что конструктор MyDerivedClass - private. Если явно поставить public - будет компилироваться (VC++ 6.0)
beaver
Posts: 10
Joined: 14 Oct 2002 18:34
Location: germany

Post by beaver »

Виртуалный же базовый класс всё и погубит :

Code: Select all


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



И всё опять хокей:)

Во всяком случае под сановскими компиляторами и под gcc
User avatar
Basil
Уже с Приветом
Posts: 8404
Joined: 06 Nov 2000 10:01
Location: SPb -> Silicon Valley, CA, USA

Post by Basil »

uncle_Pasha wrote:
yocto wrote:
uncle_Pasha wrote:Если класс не задумывался как базовый, почему бы не использовать struct?
Может это кого-то и остановит в будущем?


Так ведь объявление типа через struct не запрещает наследование.


Оно объясняет смысл данного элемента данных.
99% что никому не прийдет в голову наследоваться от struct в дальнейшем

Удачи!


struct от class отличается только доступом по умолчанию. В классе по умолчанию private, в struct - public. Все. Больше различий нет.
Lukum
Новичок
Posts: 20
Joined: 11 Oct 2002 22:40
Location: NJ

Post by Lukum »

Veselchak U wrote:
вот мне недавно задали вопрос, что произойдёт при попытке выполнения вот этого кода:

Код:

typedef struct {
char a;
char b;
} someStruct;

int
main ( int, char** )
{
someStruct ss;
someStruct* pSS;

pSS = ( someStruct* ) ss; // error - should be pSS = & ss ;
pSS->a = 'a';
printf ( "%c\n", ss.a );

return 0;
}



указания на факт, что statement pSS = ( someStruct* ) ss; в принципе -бессмыслица не принимаются.


Well, assuming that the compiler let it compile :wink:, pSS will point who knows where. If we are lucky, that pointer dereferencing will crash us. Also, depending on a machine architecture, if we have tight packing and someStruct does take only 2 bytes in memory, we may have another CPU exception -- this time missalignment.

In the worse case, we'll overwrite some random piece of memory, and this is one of the worst kinds of bugs to track... :(

Sorry for English 8)
alder
Уже с Приветом
Posts: 299
Joined: 16 Jan 2002 10:01
Location: Troy, MI

Post by alder »

A. Fig Lee wrote:Один из главных пойнов С++ - наследование, полиморфизм и так далее.

Уже давно "гром" спора отгремел, но перечитывая дискуссию, на натолкнулся на это, задевшее за живое утверждение, и не удержался от вставки своих 2с. :roll:

"и так далее" содержит в себе то, что делает С++ объектно-ориентированным языком -- инкапсуляция. Иными словами, без наследования и связанного с ним полиморфизма язык будет странным (по сегодняшним меркам), но достаточно usable ибо будет предоставлять механизм создания новых типов данных. Весьма ограниченный механизм, надо заметить, но ... очень полезный и широко применимый.

Для примера представьте хотя бы нечто вроде auto_ptr, но для специфического случая работы с неким ресурсом, тем же FILE например, который нужно не удалять, а говорить ему fclose(). С++ код использующий такой wrapper будет намного более читабельным, файл будет гарантированно закрыт, а компилятор позаботится о том, чтобы в памяти он не занял больше места чем изначальный FILE. Пример конечно притянут за уши, но, я надеюсь, что мысль понятна.

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