В настоящее время я пишу библиотеку, в которой есть несколько абстрактных классов. В дополнение к проверке того, что библиотека компилируется, я хотел бы убедиться, что все чистые виртуальные методы были определены в классах, которые должны быть конкретными. Я надеялся, что смогу получить эту информацию от nm
или objdump
, но пока не могу сказать.
Рассмотрим следующий минимальный пример:
struct A
{
void f() {}
};
struct B
{
virtual void f() = 0;
};
struct C : public B
{
void f() override {}
};
Когда я смотрю на вывод nm
, я получаю следующее. (Я исключил все, что не относится ни к одному из этих классов.)
% nm -C test.so
0000000000001174 W A::f()
000000000000118c W B::B()
000000000000118c W B::B()
0000000000001180 W C::f()
00000000000011aa W C::C()
00000000000011aa W C::C()
0000000000003db0 V typeinfo for B
0000000000003d98 V typeinfo for C
0000000000002003 V typeinfo name for B
0000000000002000 V typeinfo name for C
0000000000003d80 V vtable for B
0000000000003d68 V vtable for C
Легко отличить A
(класс без виртуальных методов) от B
и C
. Но я хочу иметь возможность различать тот факт, что B
— это абстрактный класс, а C
— конкретный.
Очевидно, что если бы у меня был список всех чисто виртуальных методов и карта иерархии классов, я мог бы пройтись по ним и проверить, определены ли они. (Выше вы можете видеть, что C::f
определено, а B::f
— нет.) Но я надеялся, что будет автоматический способ сделать это. (Я надеялся, что vtable
или typeinfo
выше будут отображаться иначе.)
Другим способом было бы добавить дополнительный файл, в котором я создаю экземпляр одного объекта из каждого класса, который, как я ожидаю, будет конкретным, и я получу ошибку компилятора, если какой-либо из них имеет неопределенные виртуальные методы. Но это тоже раздражает.
Понятно, что я не эксперт по ELF или модели объектных файлов C в целом, поэтому я был бы признателен за полезное введение или ссылку, если ответ окажется сложным.
Как программист, я должен убедиться, что я полностью определил каждый класс, который, как я ожидаю, будет создан. Есть много классов, которые мне нужно обновить, и я не уверен, что смогу сделать всю работу без единой ошибки. Было бы неплохо проверить автоматически. К сожалению, проект, над которым я работаю, не имеет «хороших» автоматических тестов и не будет в ближайшем будущем. (Есть система тестирования, но она очень медленная и, как правило, запускается незадолго до релиза.) Я хотел бы выявлять эти ошибки сейчас, а не реагировать на них позже.
Я бы посоветовал изучить инструмент clang ast для достижения того, что вы ищете. Вы, вероятно, можете превратить это в автоматическую проверку с лязгом.
Как насчет определения макроса, как показано ниже?
#include <iostream>
#include <type_traits>
#define TEST_CC(x) if (std::is_abstract<x>::value) std::cout << #x" is abstract \n"
struct A
{
void f() {}
};
struct B
{
virtual void f() = 0;
};
struct C : public B
{
void f() override {}
};
int main()
{
TEST_CC(A);
TEST_CC(B);
TEST_CC(C);
}
Это не отвечает на исходный вопрос вообще.
Я не понимаю, мое решение точно такое же, как и выше, и мое ранее. В связи с упомянутым сообщением: «Другим способом было бы добавить дополнительный файл, в котором я создаю экземпляр одного объекта из каждого класса, который, как я ожидаю, будет конкретным, и я получу ошибку компилятора, если какой-либо из них имеет неопределенные виртуальные методы. Но это тоже раздражает», мое решение решает проблему с этой стороны.
As a programmer, I need to make sure that I have fully defined every class I expect to be instantiated.
Только ты может знать, какие это классы.
если у вас есть список таких классов, вы можете создать тестовую программу следующим образом:
#include <assert.h>
#include <mylib.h>
int main()
{
assert(!std::is_abstract<Class1>::value);
...
assert(!std::is_abstract<ClassN>::value);
}
скомпилируйте и запустите его. Если это не так assert
, вы молодец.
Вы также можете теоретически сделать это на уровне test.so
. Вам нужно будет найти vtable
для каждого ожидаемого конкретного класса X
и изучить значения в этой таблице. Если какое-либо значение равно &__cxa_pure_virtual
, то класс не является конкретным.
Это усложняется тем фактом, что vtable
перемещаются во время загрузки. Таким образом, вам нужно найти vtable
, затем найти записи о перемещении, которые относятся к ним, а затем выполнить поиск __cxa_pure_virtual
.
В целом, создание тестовой программы — гораздо более простой подход.
Обновлять:
I was hoping that I could do something like
nm -C test.so | grep ... | ...
to select the names of pure virtual classes.
Это невозможно сделать.
At a glance I would be able to tell if any of them did not belong on the list.
Что вы можете сделать, так это найти классы все с виртуальными столами через nm -D test.so | grep ' _ZTV' | c++filt
. Используйте этот список всех классов для создания тестовой программы, но вместо assert
ing просто напечатайте имя класса и результат теста.
Наконец, отфильтруйте этот список только по классам, где is_abstract<T>::value
верно. Вуаля: теперь у вас есть список, который вы искали.
Хорошо, вторая половина вашего ответа убедила меня в том, что создание небольшой тестовой программы — правильный путь.
В ответ на первое предложение вашего ответа: Очевидно, я знаю, что nm
не может автоматически определить, хочу ли я, чтобы класс был конкретным. Я имел в виду, что я надеялся, что смогу сделать что-то вроде nm -C test.so | grep ... | ...
, чтобы выбрать имена чистых виртуальных классов. С первого взгляда я мог бы сказать, если какой-либо из них не принадлежит к списку.
Задача компилятора — обеспечить это. Почему вас волнует, что то, что вам передается, является базовым классом другого класса?