2016-03-03 3 views
18

В C++ 11 и более поздних версиях, как определить, является ли конструктор абстрактного базового класса noexcept? Следующие методы не работают:Определить, нет ли конструктора абстрактного базового класса?

#include <new> 
#include <type_traits> 
#include <utility> 

struct Base { Base() noexcept; virtual int f() = 0; }; 

// static assertion fails, because !std::is_constructible<Base>::value: 
static_assert(std::is_nothrow_constructible<Base>::value, ""); 

// static assertion fails, because !std::is_constructible<Base>::value: 
static_assert(std::is_nothrow_default_constructible<Base>::value, ""); 

// invalid cast to abstract class type 'Base': 
static_assert(noexcept(Base()), ""); 

// invalid new-expression of abstract class type 'Base' 
static_assert(noexcept(new (std::declval<void *>()) Base()), ""); 

// cannot call constructor 'Base::Base' directly: 
static_assert(noexcept(Base::Base()), ""); 

// invalid use of 'Base::Base': 
static_assert(noexcept(std::declval<Base &>().Base()), ""); 

Простое использование для этого было бы:

int g() noexcept; 
struct Derived: Base { 
    template <typename ... Args> 
    Derived(Args && ... args) 
      noexcept(noexcept(Base(std::forward<Args>(args)...))) 
     : Base(std::forward<Args>(args)...) 
     , m_f(g()) 
    {} 

    int f() override; 

    int m_f; 
}; 

Любые идеи о том, как это или АРХИВ возможно ли это вообще, не изменяя абстрактный базовый класс ?

PS: Любые ссылки на отчеты о дефектах ISO C++ или незавершенное производство также приветствуются.

EDIT: Как было отмечено в два раза, по умолчанию в Derived конструкторами с = default делает noexcept наследуется. Но это не решает проблему для общего случая.

+0

Если вы знаете абстрактные методы, вы можете сделать это как [это] (http://coliru.stacked-crooked.com/a/99ee522df4a1c7c7). если вы хотите полностью общее решение, я думаю, что вам не повезло – sp2danny

ответ

4

на основе skypjack's answer лучшее решением, которое не требует изменений подписи в Derived конструктора было бы определить макет подкласса Base в качестве члена личного типа из Derived и использовать конструкцию, что в Derived конструктора noexcept спецификации :

class Derived: Base { 

private: 

    struct MockDerived: Base { 
     using Base::Base; 

     // Override all pure virtual methods with dummy implementations: 
     int f() override; // No definition required 
    }; 

public: 

    template <typename ... Args> 
    Derived(Args && ... args) 
      noexcept(noexcept(MockDerived(std::forward<Args>(args)...))) 
     : Base(std::forward<Args>(args)...) 
     , m_f(g()) 
    {} 

    int f() override { return 42; } // Real implementation 

    int m_f; 

}; 
+1

Ну, я также изучил это решение, проблема в том, что это легко сделать, поскольку у вас есть один виртуальный метод, но это довольно раздражает, когда они растут в количестве, потому что вы должны определить новый и совершенно бесполезный класс используется только с условием 'noexcept'. Во всяком случае, рекомендуется, потому что все еще это жизнеспособное решение. – skypjack

+0

Да, но я думаю, что это хороший компромисс для того, чтобы сохранить подпись конструктора 'Derived' неизменной и чистой. Если публичный интерфейс 'Derived' изменился (к худшему), было бы утомительно, если бы кто-то изменил все вызовы конструктора, чтобы принять это обходное решение во внимание. – jotik

+3

Другая вещь, которую вы можете сделать, - не утруждать себя реализацией виртуальных функций вообще, даже фиктивных реализаций. Для целей необоснованного контекста их нужно только объявлять - и, вероятно, лучше, если они вообще не определены. –

1

Наивным, но рабочим примером было бы введение не виртуального базового класса и экспорт его конструктора с помощью директивы using. Вот пример:

#include<utility> 

struct BaseBase { 
    BaseBase() noexcept { } 
}; 

struct Base: public BaseBase { 
    using BaseBase::BaseBase; 
    virtual int f() = 0; 
}; 

struct Derived: public Base { 
    template <typename ... Args> 
    Derived(Args && ... args) 
     noexcept(noexcept(BaseBase(std::forward<Args>(args)...))) 
     : Base(std::forward<Args>(args)...) 
    { } 

    int f() override { } 
}; 

int main() { 
    Derived d; 
    d.f(); 
} 
+1

Это считается «изменением абстрактного базового класса», но может быть обходным путем для некоторых случаев. Все поля и функции, от которых зависит предыдущий конструктор 'Base', должны быть включены в конструктор или класс' BaseBase', и все, что зависит от чистых виртуальных методов, должно оставаться в 'Base'. Если мы считаем, что чистый метод вызывает плохую вещь, то на первый взгляд кажется, что такой рефакторинг может быть полезен. Но это все равно не помогает в общем случае. Я дам вам выкуп за усилия. – jotik

+0

@jotik Вы правы, абсолютно, вот почему я сказал * наивно, но работал *. ;-) ... Во всяком случае, действительно интересный вопрос, я все еще пытаюсь понять, как это сделать. – skypjack

+0

@jotik Я добавил еще один ответ, который более подходит, может быть, вы найдете его не обходным путем. Дайте мне знать, разрешит ли он проблему. Действительно хороший вопрос. – skypjack

7

[UPDATE: ЭТО СТОИТ Спрыгнув на EDIT РАЗДЕЛ]

Хорошо, я нашел решение, даже если он не компилируется со всеми составителями, потому что ошибки в GCC (см. this question для получения дополнительной информации).

Решение основано на наследуемых конструкторах и способах вызова функций.
Рассмотрим следующий пример:

#include <utility> 
#include <iostream> 

struct B { 
    B(int y) noexcept: x{y} { } 
    virtual void f() = 0; 
    int x; 
}; 

struct D: public B { 
private: 
    using B::B; 

public: 
    template<typename... Args> 
    D(Args... args) 
    noexcept(noexcept(D{std::forward<Args>(args)...})) 
     : B{std::forward<Args>(args)...} 
    { } 

    void f() override { std::cout << x << std::endl; } 
}; 

int main() { 
    B *b = new D{42}; 
    b->f(); 
} 

Я предполагаю, что это вполне понятно.
В любом случае, дайте мне знать, если вы обнаружите, что что-то нуждается в подробностях, и я буду рад обновить ответ.
Основная идея состоит в том, что мы можем непосредственно наследовать определение noexcept из базового класса вместе со своими конструкторами, так что мы больше не будем ссылаться на этот класс в наших операциях noexcept.

Here вы можете увидеть вышеупомянутый рабочий пример.

[EDIT]

Как из комментариев, пример страдает от проблемы, если конструкторы базового класса и производного одного имеют ту же сигнатуру.
Спасибо Петру Скотницки за то, что он указал на него.
Я собираюсь упомянуть эти комментарии, и я скопирую и вставлю предложенный вместе с ними код (с упоминанием авторов, где это необходимо).

Прежде всего, here, мы можем видеть, что пример как он не работает должным образом (благодаря Piotr Skotnicki для ссылки).
Код почти такой же, как и раньше, поэтому его не стоит копировать и вставлять сюда.
Кроме того, из того же автора, то пример that shows that the same solution works as expected under certain circumstances (см комментарии для Furter деталей):

#include <utility> 
#include <iostream> 

struct B { 
    B(int y) noexcept: x{y} 
    { 
     std::cout << "B: Am I actually called?\n"; 
    } 
    virtual void f() = 0; 
    int x; 
}; 

struct D: private B { 
private: 
    using B::B; 

public: 
    template<typename... Args> 
    D(int a, Args&&... args) 
    noexcept(noexcept(D{std::forward<Args>(args)...})) 
     : B{std::forward<Args>(args)...} 
    { 
     std::cout << "D: Am I actually called?\n"; 
    } 
    void f() override { std::cout << x << std::endl; } 
}; 

int main() 
{ 
    D* d = new D{71, 42}; 
    (void)d; 
} 

Кроме того, я предлагаю альтернативное решение, которое является немного более навязчивым и основан на идее, для которую std::allocator_arg_t обозначает, что также же предложенный Петром Скотницкого в комментарии:

это достаточно, если производный класс конструктор выигрывает в разрешении перегрузки.

Отсюда следует код упоминается here:

#include <utility> 
#include <iostream> 

struct B { 
    B(int y) noexcept: x{y} 
    { 
     std::cout << "B: Am I actually called?\n"; 
    } 
    virtual void f() = 0; 
    int x; 
}; 

struct D: public B { 
private: 
    using B::B; 

public: 
    struct D_tag { }; 

    template<typename... Args> 
    D(D_tag, Args&&... args) 
    noexcept(noexcept(D{std::forward<Args>(args)...})) 
     : B{std::forward<Args>(args)...} 
    { 
     std::cout << "D: Am I actually called?\n"; 
    } 
    void f() override { std::cout << x << std::endl; } 
}; 

int main() 
{ 
    D* d = new D{D::D_tag{}, 42}; 
    (void)d; 
} 

Спасибо еще раз Петр Скотницкого за помощь и комментарии, очень ценна.

+1

http://coliru.stacked-crooked.com/a/fe0fb0d62f8d2b95 –

+0

@PiotrSkotnicki Довольно умный, более чем я уверен. :-) ... Полкли пропустил «публичный», который все сломал. Ну, я написал с компилятором на мобильном телефоне, ожидая, когда автобус поработает, позовите меня, чтобы он не проверял его дважды один раз на работе. Сожалею. Во всяком случае, также [тот] (http://coliru.stacked-crooked.com/a/8b55cca1ee3d1176) работает не так, как ожидалось, но должен быть 'D (int)' недоступен? – skypjack

+0

проблема заключается в том, что ваше решение навязчиво в том смысле, что оно импортирует конструкторы базового класса в область производного класса, поэтому они участвуют в разрешении перегрузки, что может привести к неожиданным результатам, как я показал. кроме этого, [ваше решение действительно работает для других случаев] (http://coliru.stacked-crooked.com/a/7a7b48ef686cdc2a) –

0

Я столкнулся с той же проблемой, и решение, которое я нашел, состояло в том, чтобы реализовать дополнительные свойства.

Вы можете взглянуть на мое сообщение here.

Смежные вопросы