Здравствуйте, Alex Dav, Вы писали:
AD>Код давали одной знакомой в качестве теста — судя по тому, что я полностью был с ней согласен — я ошибки не нашел тоже (или нашел, но не те
я вижу проблему в конструкторе класса class MyAsyncAction1:
один и родителей в конструкторе запускает поток, который будет делать виртуальный вызов метода Call для объекта, который потенциально еще не сконструировался
в итоге будет pure virtual call
ну и метод std::wstring What() const throw() забавляет )
ведь он все же может кинуть исключение при копировании строки, хотя в объявлении прописано throw()
On 12/14/2011 06:10 PM, Alex Dav wrote:
> что вас смущает в данном коде? <message/4542120.aspx> Оценить
Далее тут вот:
> class MyAsyncAction1 > :public CallBase > ,public AsyncActionBase > { > public: > MyAsyncAction1() > : AsyncActionBase(this) > { > ; > };
происходит следующее:
Вызывается конструктор предка AsyncActionBase() с передачей
туда this в качестве ссылки на CallBase.
Это не всегда можно делать, поскольку в этот момент объект,
на который указывает this, ещё не сконструирован
(будет только когда управление попадёт на '{', а лучше вообще
дождаться '}' )
Так можно делать, только если this сохраняется как ссылка для
дальнейшего использования в более познее время.
С учётом того, что AsyncActionBase тут же запустит поток с использованием
виртуальных фунций этого объекта (которые ещё не проинициализированы),
то очень вероятно, что будет либо нарушение защиты памяти, либо
"pure virtual func call", либо далее реализация CallBase будет
работать какое-то время со своими неинициализированными данными.
Вообще, сама идея делать что-то в конструкторе, кроме инициализации,
порочна, а уж запускать там поток -- вообще безумие.
1) Конструктор Exception может кинуть исключение.
2) const std::wstring& Exception::What() const throw()
3) std::clog << "An error " << GetLastError() << "occured"
GetLastError может вернуть ошибку работы std::clog << "An error "
надо err = GetLastError(); std::clog << format(err); throw Exception(format(err));
А лучше переделать этот метод с минимальным использованием функций которые могут вызвать ошибку.
4) Конструктор AsyncActionBase(CallBase* pCall) вызывается с параметром this, указывающим на потомка, который еще не полностью создан, и таблица виртуальных методов не инициализирована, возможен вызов чисто виртуальной функции в запущенном потоке.
5) Деструктор ~AsyncActionBase() бросает исключение
6) в std::wcerr пишется с разных потоков без синхронизации и без << std::endl, бардак в консоли
Здравствуйте, fin_81, Вы писали:
_>Здравствуйте, Alex Dav, Вы писали:
_>1) Конструктор Exception может кинуть исключение. _>2) const std::wstring& Exception::What() const throw() _>3) std::clog << "An error " << GetLastError() << "occured" _> GetLastError может вернуть ошибку работы std::clog << "An error " _> надо err = GetLastError(); std::clog << format(err); throw Exception(format(err)); _> А лучше переделать этот метод с минимальным использованием функций которые могут вызвать ошибку. _>4) Конструктор AsyncActionBase(CallBase* pCall) вызывается с параметром this, указывающим на потомка, который еще не полностью создан, и таблица виртуальных методов не инициализирована, возможен вызов чисто виртуальной функции в запущенном потоке. _>5) Деструктор ~AsyncActionBase() бросает исключение _>6) в std::wcerr пишется с разных потоков без синхронизации и без << std::endl, бардак в консоли
4 и 5 вообще очень грубые ошибки. imho, если кандидат их не заметил, то его можно брать только на начинающего разработчика.
3 — тоже довольно известные грабли
Здравствуйте, baily, Вы писали:
B>4 и 5 вообще очень грубые ошибки. imho, если кандидат их не заметил, то его можно брать только на начинающего разработчика.
согласен B>3 — тоже довольно известные грабли
ну как бы не критичная
Здравствуйте, RiNSpy, Вы писали:
RNS>Всё. Всего-то пара строчек кода, а непонятно зачем это всё, что оно делает. Имена неговорящие, комментариев нет, какие-то константы кругом.
Ага это первое, что было отмечено, но есть великие и ужасные конторы, которые считают это ... хм, красивым(?)
Здравствуйте, Alex Dav, Вы писали:
AD>Код давали одной знакомой в качестве теста — судя по тому, что я полностью был с ней согласен — я ошибки не нашел тоже (или нашел, но не те :))
AD>#include <windows.h>
AD>#include <iostream>
AD>#include <sstream>
AD>#include <process.h>
AD>namespace TST
AD>{
AD> class Exception
AD> {
AD> public:
AD> Exception(const wchar_t* szwText) // должен быть explicit
AD> : m_wstrText(szwText)
AD> {;}; // лишние точки с запятой
AD> std::wstring What() const throw() // throw() зло
AD> {
AD> return m_wstrText;
AD> }; // лишние точки с запятой
AD> protected:
AD> const std::wstring m_wstrText;
AD> };
AD> void le_chk(BOOL bResult, const char* file, int line) // неудачное имя функции
AD> {
AD> if(!bResult)
AD> {
AD> std::clog << "An error " << GetLastError() << "occured"; // неконсистентная табуляция
AD> std::wostringstream os;
AD> os << L"Win32 error occured: GetLastError returned " << GetLastError() // два вызова GetLastError подряд, и не факт, что предыдущий вывод в clog на них не влияет
AD> << " in " << file << L"@" << line;
AD> throw Exception(os.str().c_str());
AD> }; // лишние точки с запятой
AD> }; // лишние точки с запятой
AD> #define TST_LE_CHECK(res) le_chk((res), __FILE__, __LINE__)
AD> class CallBase
AD> {
AD> public:
AD> virtual void Call() = 0;
AD> };
AD> class AsyncActionBase
AD> {
AD> public:
AD> AsyncActionBase(CallBase* pCall) // explicit
AD> : m_hThread(NULL)
AD> {
AD> unsigned uId = 0;
AD> m_hThread = (HANDLE)_beginthreadex( // C-style cast. Должен быть reinterpret_cast
AD> NULL, 0, &AsyncActionBase::thread_proc,
AD> (void*)pCall, 0, &uId); // C-style cast. Должен быть static_cast
AD> TST_LE_CHECK(!!m_hThread);
AD> }; // лишние точки с запятой
AD> virtual ~AsyncActionBase()
AD> {
AD> if(m_hThread)
AD> {
AD> TST_LE_CHECK(WaitForSingleObject(m_hThread, INFINITE)
AD> != WAIT_FAILED); // деструкторы, бросающие исключения — зло
AD> TST_LE_CHECK(CloseHandle(m_hThread)); // деструкторы, бросающие исключения — зло
AD> m_hThread = NULL;
AD> }; // лишние точки с запятой
AD> }; // лишние точки с запятой
AD> protected:
AD> static unsigned __stdcall thread_proc(void * p) // protected static — это для кого?
AD> {
AD> try
AD> {
AD> ((CallBase*)p)->Call(); // C-style cast. Должен быть static_cast
AD> }
AD> catch (const TST::Exception& excpt)
AD> {
AD> std::wcerr << excpt.What();
AD> }; // лишние точки с запятой
AD> return 0;
AD> }; // лишние точки с запятой
AD> HANDLE m_hThread;
AD> };
AD> class MyAsyncAction1
AD> : public CallBase
AD> , public AsyncActionBase
AD> {
AD> public:
AD> MyAsyncAction1()
AD> : AsyncActionBase(this) // this ещё не сконструирован, а AsyncActionBase уже вызывает на него Call
AD> {
AD> ; // лишние точки с запятой
AD> }; // лишние точки с запятой
AD> virtual ~MyAsyncAction1()
AD> {
AD> ; // лишние точки с запятой
AD> }; // лишние точки с запятой
AD> //CallBase
AD> virtual void Call()
AD> {
AD> for(size_t i = 0; i < 100; ++i)
AD> {
AD> std::cout << "Test #" << (i+1) << std::endl; // лучше было бы крутить цикл от 1 до <=100 и выводить i
AD> Sleep(10);
AD> }; // лишние точки с запятой
AD> }; // лишние точки с запятой
AD> };
AD>}; // лишние точки с запятой
AD>int main()
AD>{
AD> try
AD> {
AD> TST::MyAsyncAction1 oBj;
AD> }
AD> catch (const TST::Exception& excpt)
AD> {
AD> std::wcerr << excpt.What();
AD> }; // лишние точки с запятой
AD>}; // лишние точки с запятой
Здравствуйте, baily, Вы писали:
B>4 и 5 вообще очень грубые ошибки. imho, если кандидат их не заметил, то его можно брать только на начинающего разработчика.
Если добавить синхронизацию, то можно избежать 4
Исключение в деструкторе это еще не катастрофа. Хотя в этом примере это катастрофа.
B>3 — тоже довольно известные грабли
А то что логирование не работает, по мне, более неприятнее 4 и 5. Лучше бы вообще не было такого "дезинформатора".
Я расписал то что выглядит некрасиво. А красота она для каждого своя
Здравствуйте, RiNSpy, Вы писали:
RNS>Всё. Всего-то пара строчек кода, а непонятно зачем это всё, что оно делает. Имена неговорящие, комментариев нет, какие-то константы кругом.
А по моему, все понятно, что там делается. К стилю всегда можно попридираться.
RNS>Остальное всё вторично.
То есть для вас неговорящая константа и отсутствие комментариев более первично, чем вызов виртуального метода в деструкторе?
Здравствуйте, Alex Dav, Вы писали:
AD>Код давали одной знакомой в качестве теста — судя по тому, что я полностью был с ней согласен — я ошибки не нашел тоже (или нашел, но не те
Этот код мне напоминает старый анекдот:
Сидит программист глубоко в отладке.
Подходит сынишка:
— Папа, почему солнышко каждый день встает на востоке, а садится на западе?
— Ты это проверял?
— Проверял.
— Хорошо проверял?
— Хорошо.
— Работает?
— Работает.
— Каждый день работает?
— Да, каждый день.
— Тогда ради Бога, сынок, ничего не трогай, ничего не меняй!
Попытка в функции MyAsyncAction1::Call() использовать член класса MyAsyncAction1 приведёт к "неожиданным" проблемам...
Например, наивный программист глядя на этот класс:
class MyAsyncAction1 : public CallBase, public AsyncActionBase
{
public:
MyAsyncAction1() : AsyncActionBase(this)
{
};
virtual ~MyAsyncAction1()
{
};
//CallBasevirtual void Call()
{
for(size_t i = 0; i < 100; ++i)
{
std::cout << "Test #" << (i+1) << std::endl;
Sleep(10);
};
};
};
может захотеть заменить в MyAsyncAction1::Call() поток std::cout на член класса (ну а что такого ? он же private!):
class MyAsyncAction1 : public CallBase, public AsyncActionBase
{
public:
MyAsyncAction1() : AsyncActionBase(this)
{
};
virtual ~MyAsyncAction1()
{
std::cout << m_out.str();
};
//CallBasevirtual void Call()
{
for(size_t i = 0; i < 100; ++i)
{
m_out << "Test #" << (i+1) << std::endl;
Sleep(10);
};
};
private:
std::ostringstream m_out;
};
то-то он будет удивлён, когда запустит этот вариант!
Здравствуйте, B0FEE664, Вы писали:
BFE>то-то он будет удивлён, когда запустит этот вариант!
я тоже удивлен, т.к. не понял суть (ошибку) в вашем коде =\
а вообще это бред такие тесты имхо правильно делать так спросить в чем рубит
человек что может написать и потом дать задание написать программу чтобы
это заняло где то недельку и потом посмотреть что он там понапишет и как
имхо так все кто просто заучил кому не очень надо и прочие авантюристы
отсеятся и тут будет комплексная проверка а не только на вниманительность
сразу будет виден опыт человека а так просто влом искать сиплюсплюсные баги
специально засаженные в код главное когда ты сам пишеш не делать таких ляпов
имхо очень тупая проверка и тест в целом что можно после этого сказать о
соискателе ? ничего
Здравствуйте, jyuyjiyuijyu, Вы писали:
J>а вообще это бред такие тесты имхо правильно делать так спросить в чем рубит J>человек что может написать и потом дать задание написать программу чтобы J>это заняло где то недельку и потом посмотреть что он там понапишет и как
....
Ну это вы загнули, конечно если это будет конкурс в ген. директора газпрома, то так можно, а в рядовые програмеры — кто захочет?
Здравствуйте, Alex Dav, Вы писали: AD>Ну это вы загнули, конечно если это будет конкурс в ген. директора газпрома, то так можно, а в рядовые програмеры — кто захочет?
ну если бы я собирал себе команду то я бы смотрел реальные
программы написанные этими людьми а не тупые тесты вроде этого
я считаю это очень интересное задание для соискателя а неделя
это ничто если работа тебе нужна а твой код скажет команде о тебе
все без слов в отличии от таких "тестов" от которых толку ноль
Alex Dav:
J>>а вообще это бред такие тесты имхо правильно делать так спросить в чем рубит J>>человек что может написать и потом дать задание написать программу чтобы J>>это заняло где то недельку и потом посмотреть что он там понапишет и как AD>....
AD>Ну это вы загнули, конечно если это будет конкурс в ген. директора газпрома, то так можно, а в рядовые програмеры — кто захочет?
Если контора предлагает хорошие условия, то почему бы и нет? Ну а тест, разумеется, полезен для отсева явных чайников и неадекватов (если кандидат с оттопыренными пальцами начинает устраивать истерики по поводу целесообразности первичного теста и рассказывать о том, как бы он поступил на месте работодателя, такого надо сразу пинками под зад гнать).
Здравствуйте, uzhas, Вы писали:
BFE>>то-то он будет удивлён, когда запустит этот вариант! U>я тоже удивлен, т.к. не понял суть (ошибку) в вашем коде =\
В этом и состоит опасность такого подхода. Ошибку не видно, пока весь код не прочтёшь.
class MyAsyncAction1 : public CallBase, public AsyncActionBase
{
public:
MyAsyncAction1() : AsyncActionBase(this) // вот в этой точке,
// даже ещё раньше, в конструкторе AsyncActionBase,
// система начала запуск потока, который
// вызовет Call() этого объекта.
// Компилятор MS умеет создавать виртуальную
// таблицу до создания объекта, так что вызовется именно MyAsyncAction1::Call().
// Создание нового потока в Windows процедура долгая, так что весьма вероятно,
// что этот конструктор успеет отработать до вызова MyAsyncAction1::Call() из
// параллельного потока, но полагаться на это нельзя. Теоретически
// возможен вызов MyAsyncAction1::Call() до создания m_out, что приведёт
// к UB последствиям.
{
};
virtual ~MyAsyncAction1()
{
std::cout << m_out.str();
};// Вот в этой точке сначала вызовется деструктор m_out и m_out будет разрушен, а потом вызовется
// деструктор ~AsyncActionBase() в котором текущий поток будет ждать завершения работы MyAsyncAction1::Call().
// Возможны два варианта: 1) поток, в котором выполнялся MyAsyncAction1::Call() уже завершился к этому моменту,
// тогда всё завершится обычным образом. Но возможен и вариант 2) когда MyAsyncAction1::Call() продолжает
// выполнятся после разрушения m_out. Это ведёт к UB.
//CallBasevirtual void Call()
{
for(size_t i = 0; i < 100; ++i)
{
m_out << "Test #" << (i+1) << std::endl;
Sleep(10);
};
};
private:
std::ostringstream m_out;
};
Здравствуйте, baily, Вы писали:
RNS>>Остальное всё вторично. B>То есть для вас неговорящая константа и отсутствие комментариев более первично, чем вызов виртуального метода в деструкторе?
Это просто лакмусовая бумажка своего рода. Неговорящей константы и отсутствия комментариев достаточно, чтобы на код ревью очень быстро вынести единственно верное решение "на помоечку" (с). И не надо тратить время на вчитываение, чего и как там не так вызывается.
Здравствуйте, baily, Вы писали:
B>Какую то второстепенную фигню отметили, а главные проблемы не назвали.
AD> : AsyncActionBase(this) // this ещё не сконструирован, а AsyncActionBase уже вызывает на него Call
Главная проблема — вот. Не в том, что AsyncActionBase в конструкторе вызывает виртуальную функцию объекта, переданного параметром в конструктор — AsyncActionBase вполне мог быть рассчитан на использование с отдельным объектом — а в том, что некий объект, наследующийся от AsyncActionBase, передаёт в его конструктор недоконструированного себя.
А что конкретно из отмеченного вы считаете второстепенной фигнёй, и почему?
Здравствуйте, Alex Dav, Вы писали:
AD>Код давали одной знакомой в качестве теста — судя по тому, что я полностью был с ней согласен — я ошибки не нашел тоже (или нашел, но не те
AD>
AD> class Exception // а) не пиши ненужные классы повторяющие std::runtime_error. б) если таки пишешь - наследуй от std::exception
.....
AD> std::wostringstream os;
AD> os << L"Win32 error occured: GetLastError returned " << GetLastError()
AD> << " in " << file << L"@" << line; // мешанина из ANSI\UNICODE
...
AD> static unsigned __stdcall thread_proc(void * p)
AD> {
AD> try
AD> {
AD> ((CallBase*)p)->Call();
AD> }
AD> catch (const TST::Exception& excpt) // а кто будет ловить std::bad_alloc, и прочих std::exception. Я же говорил - наследуй от std::exception
AD> {
AD> std::wcerr << excpt.What();
AD> };
AD> return 0;
AD> };
AD>
Don't embed a std::string object or any other data member or base class whose copy constructor could throw an exception. That could lead directly to std::terminate() at the throw point. Similarly, it's a bad idea to use a base or member whose ordinary constructor(s) might throw, because, though not necessarily fatal to your program, you may report a different exception than intended from a throw-expression that includes construction such as:
throw some_exception();
There are various ways to avoid copying string objects when exceptions are copied, including embedding a fixed-length buffer in the exception object, or managing strings via reference-counting. However, consider the next point before pursuing either of these approaches.
Здравствуйте, Caracrist, Вы писали:
C>Здравствуйте, Caracrist, Вы писали:
C>>Здравствуйте, Alex Dav,
C>>время жизни потока и объекта никак не связанны
+1
C>
C> class CallBase
C> {
C> public:
C> virtual void Call() = 0;
C> }; // а где виртуальный деструктор?
C>
Значение виртуальных деструкторов сильно преувеличено, при использовании умных указателей, знающих как удалять объект (например, boost::shared_ptr/std::shared_ptr) можно прекрасно обходиться без виртуальных деструкторов.
C>
C> class AsyncActionBase
C> {
C> public:
C> AsyncActionBase(CallBase* pCall)
C> : m_hThread(NULL)
C> {
C> unsigned uId = 0;
C> m_hThread = (HANDLE)_beginthreadex( // зачем (чтоб закрыть с CloseHandle(?)) пользоваться _beginthreadex и плодить не нужные сущьности uId, когда есть _beginthread
C> NULL, 0, &AsyncActionBase::thread_proc,
C> (void*)pCall, 0, &uId);
C>
_beginthreadex тут, видимо, для того чтобы можно было дождаться окончания работы нити.
C>
C> TST_LE_CHECK(!!m_hThread); // _beginthreadex это не CreateThread(), и GetLastError() тут бесполезен; смотреть _get_errno, и внимание сюрприз: returns -1L on an error
C> }; // мусор: ";"
C>
_beginthreadex это не _beginthread, в отличие от последнего, при ошибке возвращает 0 а не -1. см. здесь
Remarks
...
_beginthreadex returns 0 on failure, rather than -1L.
...
BFE> class MyAsyncAction1 : public CallBase, public AsyncActionBase
BFE> {
BFE> public:
BFE> MyAsyncAction1() : AsyncActionBase(this) // вот в этой точке,
BFE> // даже ещё раньше, в конструкторе AsyncActionBase,
BFE> // система начала запуск потока, который
BFE> // вызовет Call() этого объекта.
BFE> // Компилятор MS умеет создавать виртуальную
BFE> // таблицу до создания объекта, так что вызовется именно MyAsyncAction1::Call().
BFE>
А можно пруф? При компиляции VC2010 подобный код падает с "pure virtual function call", кроме того, несмотря на то, что по стандарту, имеем UB и, формально, компилятор вправе сгенерировать код вызова MyAsyncAction1::Call, это выглядело бы странно, потому что, для не чисто-виртуальных функций этого делать нельзя и, например, typeid(*this) вызванный в конструкторе Call должен будет вернуть type_info для класс Call, а не MyAsyncAction1.
Здравствуйте, Centaur, Вы писали:
C>Здравствуйте, baily, Вы писали:
B>>Какую то второстепенную фигню отметили, а главные проблемы не назвали.
C>
AD>> : AsyncActionBase(this) // this ещё не сконструирован, а AsyncActionBase уже вызывает на него Call
C>
C>Главная проблема — вот. Не в том, что AsyncActionBase в конструкторе вызывает виртуальную функцию объекта, переданного параметром в конструктор — AsyncActionBase вполне мог быть рассчитан на использование с отдельным объектом — а в том, что некий объект, наследующийся от AsyncActionBase, передаёт в его конструктор недоконструированного себя.
Так как. Интересно. То есть вы считаете, что this вообще нельзя передавать в конструктор базового класса?
Ведь передать туда "доконструируемое" себя не получится в принципе.
C>А что конкретно из отмеченного вы считаете второстепенной фигнёй, и почему?
наиболее грамотным в этой ветке, перечисляющий все ключевые недостатки.
Там стоило только указать, что особенно 4 и 5 являются наиболее злостными. Ну еще и 3.
Здравствуйте, k.o., Вы писали:
KO>Значение виртуальных деструкторов сильно преувеличено, при использовании умных указателей, знающих как удалять объект (например, boost::shared_ptr/std::shared_ptr) можно прекрасно обходиться без виртуальных деструкторов.
Это так, но при этом деструктор лучше делать защищенным, все-таки, иначе чревато. Саттер и Александреску в своей книге дают рекомендацию, которая выглядит буквально так: "Делайте деструкторы базовых классов открытыми и виртуальными либо защищенными и невиртуальными".
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, k.o., Вы писали:
KO>>Значение виртуальных деструкторов сильно преувеличено, при использовании умных указателей, знающих как удалять объект (например, boost::shared_ptr/std::shared_ptr) можно прекрасно обходиться без виртуальных деструкторов.
R>Это так, но при этом деструктор лучше делать защищенным, все-таки, иначе чревато. Саттер и Александреску в своей книге дают рекомендацию, которая выглядит буквально так: "Делайте деструкторы базовых классов открытыми и виртуальными либо защищенными и невиртуальными".
Теоретически, всё верно, но на практике оказалось что это не так уж и чревато: большая система, куча legacy кода с более чем 10 летней историей, куча абстрактных классов без виртуальных или защищенных деструкторов и ни одной вызванной этим ошибки. В общем, правильная архитектура оказалась полезней следованию советов из книжки, которой ещё не было когда этот код начинали писать.
AD>>> : AsyncActionBase(this) // this ещё не сконструирован, а AsyncActionBase уже вызывает на него Call
C>>Главная проблема — вот. Не в том, что AsyncActionBase в конструкторе вызывает виртуальную функцию объекта, переданного параметром в конструктор — AsyncActionBase вполне мог быть рассчитан на использование с отдельным объектом — а в том, что некий объект, наследующийся от AsyncActionBase, передаёт в его конструктор недоконструированного себя.
B>Так как. Интересно. То есть вы считаете, что this вообще нельзя передавать в конструктор базового класса?
С++11 [basic.life]#5:
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated […] any pointer that refers to the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see 12.7.[…]
C++11 [class.cdtor]#3:
To explicitly or implicitly convert a pointer (a glvalue) referring to an object of class X to a pointer (reference) to a direct or indirect base class B of X, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive from B shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior.
С этим тут всё нормально — само получение указателя на базовый класс CallBase валидно.
Там же #4:
Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). […] If the virtual function call uses an explicit class member access (->) and the object expression refers to the complete object of x or one of that object’s base class subobjects but not x or one of its base class subobjects, the behavior is undefined.
По-моему, ровно наш случай. Явный вызов виртуальной функции из другой ветви наследования во время конструирования.
То, что объект недоконструированный, важно ровно потому, что в том состоянии, в каком он находится к моменту виртуального вызова, он ещё не является экземпляром класса, реализующего нужную виртуальную функцию. Фактически, конструктору AsyncActionBase подсунули экземпляр абстрактного класса CallBase.
C>>А что конкретно из отмеченного вы считаете второстепенной фигнёй, и почему?
B>Все из отмеченного считаю.
А на мой взгляд, соискатель, не обративший внимание на отсутствие explicit, на использование C-style кастов и на бросание исключений из деструктора, как минимум, не знаком с хорошими практиками. И, возможно, будет и сам допускать подобные ошибки.
Некоторые комментарии смущают не меньше, чем сам код В частности, многажды повторенное замечание про исключение в деструкторе (IMHO, в данном случае если выполнено одно из условий (в деструкторе), при котором выбрасывается исключение, то программе уже кирдык, т.к. в отсутствие программных ошибок эти условия не должны выполняться в принципе (если, конечно, проектировать не через ж.), а ежели они всё-таки выполняются, то это будет свидетельством того, что программа ведёт себя непредсказуемым образом; по правилам C++11 такой деструктор имеет спецификацию исключений noexcept(true) и поэтому попытка выброса исключения из него должна будет привести к незамедлительному вызову функции std::terminate).
Здравствуйте, k.o., Вы писали:
R>>Это так, но при этом деструктор лучше делать защищенным, все-таки, иначе чревато. Саттер и Александреску в своей книге дают рекомендацию, которая выглядит буквально так: "Делайте деструкторы базовых классов открытыми и виртуальными либо защищенными и невиртуальными".
KO>Теоретически, всё верно, но на практике оказалось что это не так уж и чревато: большая система, куча legacy кода с более чем 10 летней историей, куча абстрактных классов без виртуальных или защищенных деструкторов и ни одной вызванной этим ошибки. В общем, правильная архитектура оказалась полезней следованию советов из книжки, которой ещё не было когда этот код начинали писать.
Я бы согласился, если бы речь шла о классах, в принципе не проектировавшихся для полиморфного использования. Примеров таких классов великое множество: std::string, std::vector, boost::shared_ptr и пр. и др. Но когда речь идет об абстрактных классах, то лучше не провоцировать ошибки там, где от них не трудно защититься. Я понимаю, legacy код, ошибки молодости и все такое... Но нельзя же это утверждать как норму.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Alex Dav, Вы писали:
AD>Код давали одной знакомой в качестве теста — судя по тому, что я полностью был с ней согласен — я ошибки не нашел тоже (или нашел, но не те
О, никто про это не написал: Exception(const wchar_t* szwText) : m_wstrText(szwText), если const wchar_t* szwText будет 0, то упадет, std::wstring не любит ноль принимать в качестве const wchar_t*. Т.к. в этой программе сюда ноль точно не передается, то ошибка потенциальная.
Здравствуйте, k.o., Вы писали:
BFE>>// Компилятор MS умеет создавать виртуальную BFE>>// таблицу до создания объекта, так что вызовется именно MyAsyncAction1::Call().
KO>А можно пруф? При компиляции VC2010 подобный код падает с "pure virtual function call", кроме того, несмотря на то, что по стандарту, имеем UB и, формально, компилятор вправе сгенерировать код вызова MyAsyncAction1::Call, это выглядело бы странно, потому что, для не чисто-виртуальных функций этого делать нельзя и, например, typeid(*this) вызванный в конструкторе Call должен будет вернуть type_info для класс Call, а не MyAsyncAction1.
К сожалению я должен признать, что скорее всего я ошибся или мои знания устарели. Я помню, что этот вопрос я изучал для студии Visual Studio 6.0 (1998) и мне кажется, что была специальная опция, которая позволяла отключить создание vtable до вызова конструктора. Вероятно я ошибаюсь.
Что же касается данного примера, то на моей машине конструктор всегда успевает отработать раньше, чем запустится запустится нитка и вызовется MyAsyncAction1::Call(). Так что "pure virtual function call" я не получил.
On 12/15/2011 12:16 AM, jyuyjiyuijyu wrote:
> специально засаженные в код главное когда ты сам пишеш не делать таких ляпов > имхо очень тупая проверка и тест в целом что можно после этого сказать о > соискателе ? ничего
Тест достаточно хороший. Если это тест. И ляп такой сделать очень просто.
Достаточно не вдумываться в функциональность предка и иметь привычку
инициализировать в инициализаторах.
На самом деле тут этот AsyncActionBase уже закладывает своим дизайном
такую ошибку. У него нет дефолтного конструктора, так что указать Callable
ему нужно, и именно в инициализаторе.
On 12/15/2011 01:00 PM, Masterkent wrote:
> ежели они всё-таки выполняются, то это будет свидетельством того, что программа > ведёт себя непредсказуемым образом; по правилам C++11 такой деструктор имеет > спецификацию исключений noexcept(true) и поэтому попытка выброса исключения из > него должна будет привести к незамедлительному вызову функции std::terminate).
Всё это правильно, но логика кода написанного вроде бы диктует, что пользователь
ожидает от программы другое поведение.
Оно конечно, нужно бы иметь формальную спецификацию ...
1) Конструктор AsyncActionBase: если там возникнет исключение (которое генерится TST_LE_CHECK), деструктор этого обьекта вызван не будет.
2) Деструктор AsyncActionBase: он бросается исключениями, ай-яй-яй как нехорошо
3) Вызов виртуального метода Call() по сути дела из конструктора (хоть и в другой нитке), ай-яй-яй как нехорошо.
ЗЫ. Специально не смотрел ответы выше по нитке. Если дубликаты — звиняйте.
__________
16.There is no cause so right that one cannot find a fool following it.
Здравствуйте, Lorenzo_LAMAS, Вы писали:
RNS>>Имена неговорящие, комментариев нет, какие-то константы кругом.
L_L>Ну и что, ты бы на собеседовании так и сказал, код у вас какой-то голимый и некрасивый, хер разберешь, мне в лом?
Как минимум, можно спросить, всегда они так пишут, или это они специально в тестовых целях написали ужос-ужос?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, antonio_banderas, Вы писали:
_>Здравствуйте, Alex Dav, Вы писали:
_>О, никто про это не написал: Exception(const wchar_t* szwText) : m_wstrText(szwText), если const wchar_t* szwText будет 0, то упадет, std::wstring не любит ноль принимать в качестве const wchar_t*. Т.к. в этой программе сюда ноль точно не передается, то ошибка потенциальная.
А std::basic_string приводящий к падению если в конструктор передали 0-й указатель не смущает? Если это и считать ошибкой, то не класса Exception, а, как раз, std::basic_string'а.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, k.o., Вы писали:
R>>>Это так, но при этом деструктор лучше делать защищенным, все-таки, иначе чревато. Саттер и Александреску в своей книге дают рекомендацию, которая выглядит буквально так: "Делайте деструкторы базовых классов открытыми и виртуальными либо защищенными и невиртуальными".
KO>>Теоретически, всё верно, но на практике оказалось что это не так уж и чревато: большая система, куча legacy кода с более чем 10 летней историей, куча абстрактных классов без виртуальных или защищенных деструкторов и ни одной вызванной этим ошибки. В общем, правильная архитектура оказалась полезней следованию советов из книжки, которой ещё не было когда этот код начинали писать.
R>Я бы согласился, если бы речь шла о классах, в принципе не проектировавшихся для полиморфного использования. Примеров таких классов великое множество: std::string, std::vector, boost::shared_ptr и пр. и др. Но когда речь идет об абстрактных классах, то лучше не провоцировать ошибки там, где от них не трудно защититься. Я понимаю, legacy код, ошибки молодости и все такое... Но нельзя же это утверждать как норму.
Я не утверждал что это норма, моё утверждение, дословно, было таким:
Значение виртуальных деструкторов сильно преувеличено, при использовании умных указателей, знающих как удалять объект (например, boost::shared_ptr/std::shared_ptr) можно прекрасно обходиться без виртуальных деструкторов.
Ну и основано оно было на личном опыте, когда несмотря на "legacy код, ошибки молодости и все такое" ошибок связанных с отсутствием виртуальных деструкторов не было. Собственно, смысл в том, что несмотря на то, что совет из вышеупомянутой книги, в принципе, верный, вероятность ошибки при его невыполнении, всё-равно, ничтожна.
Здравствуйте, k.o., Вы писали:
KO>Здравствуйте, antonio_banderas, Вы писали:
_>>Здравствуйте, Alex Dav, Вы писали:
_>>О, никто про это не написал: Exception(const wchar_t* szwText) : m_wstrText(szwText), если const wchar_t* szwText будет 0, то упадет, std::wstring не любит ноль принимать в качестве const wchar_t*. Т.к. в этой программе сюда ноль точно не передается, то ошибка потенциальная.
KO>А std::basic_string приводящий к падению если в конструктор передали 0-й указатель не смущает? Если это и считать ошибкой, то не класса Exception, а, как раз, std::basic_string'а.
Вообще, смущает, нехорошо так себя вести (про std::basic_string). Возможно, их можно оправдать в плане оптимизации быстродействия, типа зачем лишнюю проверку ставить (хотя польза от такой оптимизации сомнительная), больше оправданий я тут не вижу.
Но в любом случае, std::basic_string имеется такой, какой есть, и давать ему в конструктор не проверяя полученный извне const wchar_t* — это потенциальная возможность нарваться на падение.
Здравствуйте, antonio_banderas, Вы писали:
_>Здравствуйте, k.o., Вы писали:
KO>>Здравствуйте, antonio_banderas, Вы писали:
_>>>Здравствуйте, Alex Dav, Вы писали:
_>>>О, никто про это не написал: Exception(const wchar_t* szwText) : m_wstrText(szwText), если const wchar_t* szwText будет 0, то упадет, std::wstring не любит ноль принимать в качестве const wchar_t*. Т.к. в этой программе сюда ноль точно не передается, то ошибка потенциальная.
KO>>А std::basic_string приводящий к падению если в конструктор передали 0-й указатель не смущает? Если это и считать ошибкой, то не класса Exception, а, как раз, std::basic_string'а.
_>Вообще, смущает, нехорошо так себя вести (про std::basic_string). Возможно, их можно оправдать в плане оптимизации быстродействия, типа зачем лишнюю проверку ставить (хотя польза от такой оптимизации сомнительная), больше оправданий я тут не вижу.
_>Но в любом случае, std::basic_string имеется такой, какой есть, и давать ему в конструктор не проверяя полученный извне const wchar_t* — это потенциальная возможность нарваться на падение.
Ну, т.е., в данном отношении, код не хуже чем в стандартной библиотеке. Вообще это обычное дело для библиотек в C и C++ — при невыполнении предусловий получаем UB.
Здравствуйте, k.o., Вы писали:
KO>>>Теоретически, всё верно, но на практике оказалось что это не так уж и чревато...
Не ради флейма... Просто тема интересная, для меня по крайней мере. Цитаты немного перекроЮ для связности мысли...
KO>...моё утверждение, дословно, было таким: KO>
KO>Значение виртуальных деструкторов сильно преувеличено, при использовании умных указателей, знающих как удалять объект (например, boost::shared_ptr/std::shared_ptr) можно прекрасно обходиться без виртуальных деструкторов.
Тут +1 и никаких возражений с моей стороны
R>>...Но когда речь идет об абстрактных классах, то лучше не провоцировать ошибки там, где от них не трудно защититься. Я понимаю, legacy код, ошибки молодости и все такое... Но нельзя же это утверждать как норму. KO>Я не утверждал что это норма... KO>...Собственно, смысл в том, что несмотря на то, что совет из вышеупомянутой книги, в принципе, верный, вероятность ошибки при его невыполнении, всё-равно, ничтожна.
А вот тут как-то... С одной стороны, совет вроде и принимается, с другой — необходимость его соблюдения поставлена под сомнение — еще чуть-чуть и его несоблюдение становится нормой.
"вероятность ошибки при его невыполнении ничтожна" только при соблюдении некоторых условий: 1) нормальная архитектура и 2) пользователем классов является сам разработчик (и никто другой). А что если по прошествии нескольких месяцев (или лет) одно из этих условий окажется нарушенным? Ошибки, которые могут при этом возникнуть могут быть достаточно коварными.
Я вот, например, при проектировании нового класса давно выработал привычку представлять, что его пользователем может быть кто угодно: и Криворукий и Безголовый, даже в тех случаях когда мне кажется, что кроме меня этими классами никто пользоваться не будет. И вы знаете, при таком подходе обычно и появляются сущности, которые хочется использовать повторно в разных проектах.
Конечно, стопроцентную защиту от дурака сделать трудно, если вообще возможно, дурак все равно найдет способ выстрелить себе в ногу, и все равно при этом будет искать виноватых вокруг себя. И при любом проектировании, имхо, следует стремиться, чтобы, во-первых, оставить дураку минимум возможностей выстрелить себе в ногу, а, во-вторых, когда он таки выстрелит, быстро показать ему что дурак — он сам.
--
Справедливость выше закона. А человечность выше справедливости.
On 12/15/2011 02:50 PM, B0FEE664 wrote:
> К сожалению я должен признать, что скорее всего я ошибся или мои знания > устарели. Я помню, что этот вопрос я изучал для студии Visual Studio 6.0 (1998) > и *мне кажется*, что была специальная опция, которая позволяла отключить > создание vtable до вызова конструктора. Вероятно я ошибаюсь.
VTable -то создаётся ещё при старте программы. А вот указатели на них
инициализируются в конструкторе. И никогда не было чего-то другого.
Здравствуйте, rg45, Вы писали:
R>>>...Но когда речь идет об абстрактных классах, то лучше не провоцировать ошибки там, где от них не трудно защититься. Я понимаю, legacy код, ошибки молодости и все такое... Но нельзя же это утверждать как норму. KO>>Я не утверждал что это норма... KO>>...Собственно, смысл в том, что несмотря на то, что совет из вышеупомянутой книги, в принципе, верный, вероятность ошибки при его невыполнении, всё-равно, ничтожна.
R>А вот тут как-то... С одной стороны, совет вроде и принимается, с другой — необходимость его соблюдения поставлена под сомнение — еще чуть-чуть и его несоблюдение становится нормой.
Ну а с третьей стороны
There is no substitute for intelligence, experience, common sense, and good taste.
Если пункт 1 не выполняется, то проблем и так будет много, а вот 2-е условие как раз необязательно. В том-то и дело, что у меня перед глазами проект с многолетней историей, прошедший через руки множества разработчиков самой разной квалификации, и, несмотря на это, никаких проблем по части деструкторов. Честно говоря, сам был крайне удивлен когда столкнулся с таким вопиющим нарушением "рекомендаций лучших собаководов". Единственный, пожалуй, нюанс это то, что этот код не относится к API, и совсем уж дебилов к нему можно не подпускать. Если же с кодом будут работать сторонние разработчики, этот совет будет более актуальным, этакий "Framework Design Guideline" для C++.
Здравствуйте, antonio_banderas, Вы писали:
KO>>А std::basic_string приводящий к падению если в конструктор передали 0-й указатель не смущает? ;) Если это и считать ошибкой, то не класса Exception, а, как раз, std::basic_string'а.
_>Вообще, смущает, нехорошо так себя вести (про std::basic_string). Возможно, их можно оправдать в плане оптимизации быстродействия, типа зачем лишнюю проверку ставить (хотя польза от такой оптимизации сомнительная), больше оправданий я тут не вижу.
_>Но в любом случае, std::basic_string имеется такой, какой есть, и давать ему в конструктор не проверяя полученный извне const wchar_t* — это потенциальная возможность нарваться на падение.
Это всё нормально. Есть правила — нельзя делить на нуль, нельзя брать вещественнозначный корень от отрицательного числа, нельзя разыменовывать нулевой указатель. Нельзя вызывать библиотечные или свои функции с такими параметрами, которые приведут к делению на нуль, взятию корня из отрицательного числа, разыменованию нулевого указателя или вызову библиотечных функций с такими параметрами, которые приведут.
Всем известно, что конструктор std::string с параметром char const * попытается скопировать себе оттуда содержимое, пока не наткнётся на завершающий ноль. И никому не приходит в голову передавать нулевой указатель, невалидный указатель или указатель на строку, не завершённую нулём. А если кому-то куда-то приходит, то, во-первых, это не голова; и во-вторых, сами виноваты.
Здравствуйте, Masterkent, Вы писали:
M>Если контора предлагает хорошие условия, то почему бы и нет? Ну а тест, разумеется, полезен для отсева явных чайников и неадекватов (если кандидат с оттопыренными пальцами начинает устраивать истерики по поводу целесообразности первичного теста и рассказывать о том, как бы он поступил на месте работодателя, такого надо сразу пинками под зад гнать).
У меня летом приходило по 3 предложения о работе в день, если бы я на каждого тратил по неделе...
С другой стороны, если вы не ищете работу сейчас и тут появилась вакансия в компании-мечте, то можно и сделать — в качестве развлечения.
Здравствуйте, MasterZiv, Вы писали:
>> К сожалению я должен признать, что скорее всего я ошибся или мои знания >> устарели. Я помню, что этот вопрос я изучал для студии Visual Studio 6.0 (1998) >> и *мне кажется*, что была специальная опция, которая позволяла отключить >> создание vtable до вызова конструктора. Вероятно я ошибаюсь.
MZ>VTable -то создаётся ещё при старте программы. А вот указатели на них MZ>инициализируются в конструкторе. И никогда не было чего-то другого.
The Microsoft-specific __declspec(novtable) option instructs the compiler not to initialize the virtual function pointer in the constructor of the given object. Normally, this would be a "bad thing." However, for abstract classes, there's no reason to initialize the pointer, because it will always be properly initialized when a concrete class derived from the object is constructed.
By the way, this option is misnamed. It sounds like it removes the vtable itself, which isn't at all true. The option should be called noinitvtable.
Здравствуйте, rg45, Вы писали: R>Я вот, например, при проектировании нового класса давно выработал привычку представлять, что его пользователем может быть кто угодно: и Криворукий и Безголовый, даже в тех случаях когда мне кажется, что кроме меня этими классами никто пользоваться не будет. И вы знаете, при таком подходе обычно и появляются сущности, которые хочется использовать повторно в разных проектах.
зачем когда делаеш для себя писать такой огромный оверхед ? если бы я думал что любой безголовый сможет моим классом пользоваться то я бы все время тратил на укрепление брони а не на решение задачи вот например когда пишеш код для себя можно вообще очень тонкий код писать решающий только твою задачу который упадет при неправильном использовании но это как говорится сам дурак с нулевым оверхедом имхо это нормально если ты не пишеш библиотеку для всеобщего пользования я вообще не представляю как решать задачу и все время тратить на укреплении брони от фантомного вредителя так рождаются уроды там где они не нужны