Добрый час.
| "Есть такой код." |
| #include <iostream>
class Object
{
public:
Object()
: p(nullptr)
{
int n = 0;
n++;
}
virtual ~Object()
{
if (p) {
delete p;
}
}
private:
int * p;
};
class IPrimitive : public virtual Object
{
};
class ITrailerable : public virtual Object
{
};
class IStableInfo : public virtual Object
{
};
class Primitive : public virtual IPrimitive, public virtual ITrailerable
{
private:
int arr[100];
};
class IObject : public virtual Object
{
};
class ISerializable : public virtual Object
{
};
class IntermObject : public Primitive, public virtual IObject, public virtual ISerializable, public IStableInfo
{
public:
IntermObject()
{
}
IntermObject(int v)
: IntermObject()
{
throw "uups";
}
IntermObject(int v, int t)
: IntermObject(v)
{
}
~IntermObject()
{
int n = 0;
n++;
}
private:
int n;
bool b;
};
class IPage : public virtual ITrailerable
{
};
class IPages : public virtual Object
{
};
class IPageTreeNode : public virtual IObject, public IPage, public IPages
{
};
class PageTreeNode final : public IntermObject, public IPageTreeNode
{
public:
PageTreeNode()
: IntermObject(10, 20)
{}
};
int main()
{
std::cout << "Hello !\n";
try
{
new PageTreeNode();
}
catch (...)
{[img][/img]
std::cout << "Catch!\n";
int n = 0;
n++;
}
std::cout << "World!\n";
}
|
| |
Проблема в том что при выборосе исключения:
IntermObject(int v)
: IntermObject()
{
throw "uups";
}
В деструкторе Object происходит AV (Windows — cl) или Seg.Fault (Linux — gcc)
| "Из за неправильного смещения:" |
| |
| |
Вот такое изменение:
class PageTreeNode final : public IntermObject, public virtual IPageTreeNode
Исправляет проблему с падением, но приводит к двойному вызову деструктора ~Object() (конструктор вызывается 1 раз)
Мне не очень понятно зачем здесь виртуальное наследование, и правы ли компиляторы?
Другой пример:
struct Object
{
Object() = default;
virtual ~Object() = default;
};
struct IntermObject : public virtual Object
{
IntermObject() = default;
IntermObject(int v) : IntermObject() {}
};
struct PageTreeNode : public IntermObject
{
PageTreeNode() : IntermObject(1) {}
};
int main()
{
new PageTreeNode();
}
Уже не приводит к seg. fault у gcc, но приводит к AV у cl (при запуске исполняемого файла).
Замена = default на обычную имплементацию "по старинке" решает проблему с cl.
cl.exe — Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27031.1
gcc — из
https://www.onlinegdb.com
Здравствуйте, nen777w, Вы писали:
GCC 9.2 отрабатывает корректно:
https://gcc.godbolt.org/z/pCCLP-
MSVC 16.2 и 16.3 падает.
Нужно запостить баг:
https://developercommunity.visualstudio.com/
struct Object
{
Object() = default;
virtual ~Object() = default;
};
struct IntermObject : public virtual Object
{
IntermObject() = default;
IntermObject(int v) : IntermObject() {}
};
struct PageTreeNode : public IntermObject
{
PageTreeNode() : IntermObject(1) {}
};
int main()
{
new PageTreeNode();
}
Более короткий пример на первый кейс (коллега подсказал):
struct A
{
void* p = nullptr;
virtual ~A()
{
assert(p == nullptr);
}
};
struct B : public virtual A {};
struct C : public virtual A
{
C() {}
C(int v) : C() { throw 1; }
};
struct D : public C, public B
{
D() : C(1) {}
};
int main()
{
try
{
D d;
}
catch (int)
{
}
return 0;
}
Здравствуйте, nen777w, Вы писали:
N>Более короткий пример на первый кейс (коллега подсказал):
Пора переходить на clang, там всё собирается как надо и ничего не падает
Здравствуйте, _NN_, Вы писали:
_NN>Здравствуйте, nen777w, Вы писали:
N>>Более короткий пример на первый кейс (коллега подсказал):
_NN>Говорят бага нет.
Коллега, по некоторым Вашим вопросам я не могу ответить, но ваш пример в баг трекере имеет следующую особенность: при попытке создания метода D компилер сначала вызывает конструктор A, потом конструктор C, в котором происходит исключение и цепочка вызова конструкторов прерывается — не вызывается конструктор B (потому что он позже наследуется) и D. После исключения вызываются деструкторы в обратном порядке: Деструктор C потом А. Единственное, что я вообще пока не понял, почему деструктор A вообще вызывается дважды. Ведь по идее, благодаря виртуальному наследованию класс A только один, и поэтому и деструктор должен вызываться только один раз. Буду думать. А вот что при втором вызове выводит 0, а не 1 — ничего удивительного: ведь вызывается деструктор уже уничтоженного класса, и в нём может быть что угодно, вероятно компилер зануляет класс.
По той же причине из примера можно выкинуть класс D, и даже класс B. Всё равно они не принимают участие в банкете.
Вот для вдохновения ссыль на код и результаты, чтобы не было сомнений в моих словах