質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

コンストラクタ

オブジェクト指向言語において、オブジェクトを生成時に呼び出され、データの初期化などを行なう関数・メソッドのことである。

コンパイルエラー

コンパイルのフェーズで生成されるエラーです。よく無効なシンタックスやタイプが含まれているとき発生します。

継承

継承(インヘリタンス)はオブジェクト指向プログラミングに存在するシステムです。継承はオブジェクトが各自定義する必要をなくし、継承元のオブジェクトで定義されている内容を引き継ぎます。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

2回答

662閲覧

継承先のクラスが使える機能を制限したい

tails

総合スコア22

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

コンストラクタ

オブジェクト指向言語において、オブジェクトを生成時に呼び出され、データの初期化などを行なう関数・メソッドのことである。

コンパイルエラー

コンパイルのフェーズで生成されるエラーです。よく無効なシンタックスやタイプが含まれているとき発生します。

継承

継承(インヘリタンス)はオブジェクト指向プログラミングに存在するシステムです。継承はオブジェクトが各自定義する必要をなくし、継承元のオブジェクトで定義されている内容を引き継ぎます。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

0クリップ

投稿2020/06/23 14:49

問題点と解決したい点

次のコードを見て下さい。

C++

1enum class Graphic { A, B, C }; 2 3class Base { 4protected: 5 // template <typename U = decltype(this)> 6 // U は Derived になって欲しい… 7 Base() { 8 ResourceManager.preload(U::Graphics); 9 } 10 DrawGraphic(Graphic graphic) { 11 Draw(ResoureManager.getGraphicHandle(graphic)); 12 } 13}; 14class Derived : Base { 15public: 16 static constexpr auto Graphics = {Graphic::B, Graphic::C}; 17 void exec() { 18 DrawGraphic(Graphic::A); // コンパイル時エラーにしたい 19 DrawGraphic(Graphic::B); // 成功 20 DrawGraphic(Graphic::C); // 成功 21 } 22};

このような感じで、画像はA,B,Cの全3種類あり、実際にそれらのリソース管理(読み込み、破棄等)はResourceManagerに任せています。
このとき、Base から派生したすべてのクラスは、そのクラス内で使用する画像の一覧を何らかの形で持っており、必ずコンストラクタで ResourceManager の preload に渡したい。
また、DrawGraphic についてはどの派生先クラスでも同じように Draw するため、Base で定義しています。

Graphics の型は auto(std::initializer_list<Graphic>) になっていますが、なんでも良いです。
とにかく、画像B, C のみが使えることを明示できれば良いです。

質問

どのように基底クラスのコンストラクタに、派生クラスが使用するリソースを伝えれば、DrawGraphic 関数の呼び出しの際に、Graphic::A に対してコンパイル時エラーを生成できますか。

蛇足

そもそも、やることが共通しているからといって、継承で解決しようとするのが間違いですか?
クラス定義の際に、継承先のクラスのコンストラクタでいちいち preload を書いていると忘れる可能性が高いし、その他にやることが増えたり減ったりするかもしれないので、基底クラスでやろうと思いましたが…

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

こういう感じではどうでしょう

c++

1template<Graphic T> 2class Drawable 3{ 4public: 5 Drawable() { 6 preload(T); 7 } 8 void draw() { 9 Draw(getGraphicHandle(T)); 10 } 11}; 12 13class Derived : Drawable<Graphic::B>, Drawable<Graphic::C> 14{ 15 void exec() { 16 //Drawable<Graphic::A>::draw(); // Error 17 Drawable<Graphic::B>::draw(); // OK 18 Drawable<Graphic::C>::draw(); // OK 19 } 20};

そもそも、やることが共通しているからといって、継承で解決しようとするのが間違いですか?

100%ではないですが、間違っていることが多いです。


「継承を使うのは間違い」とか言いながら継承使ってました。すみません。
使わないほうがシンプルでいいですので、追記しておきます。

c++

1class Derived 2{ 3 Drawable<Graphic::B> b; 4 Drawable<Graphic::C> c; 5 void exec() { 6 //a.draw(); // Error 7 b.draw(); // OK 8 c.draw(); // OK 9 } 10};

投稿2020/06/23 16:10

編集2020/06/23 23:03
yuki23

総合スコア1448

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tails

2020/06/28 10:53

こちらの方が、よりスマートかなと思いました! ただ、enum class に対して何かをしたい場合、描画可能であれば Drawable クラス、~可能であれば ~able クラスと、やりたいことの判定に対してクラスが増えていくので、ちょっとやりにくいかなと思いました。
yuki23

2020/06/29 02:26

それならば、そもそもenum class を使うべきかどうかから考慮し直すべきです。 Baseクラスと本質的に無関係な処理をBaseクラスに詰め込んでいくと、どんどん肥大化していつか破綻することになります。一度痛い目にあってみればおわかりいただけるでしょう。
guest

0

ベストアンサー

こんにちは。

コンパイル時エラーにしたいということは、DrawGraphic()へ渡すパラメータはテンプレート引数で渡すのが妥当です。(関数の引数は動的にも渡せますので、そのままではコンパイル時エラーにできないです。)

UにDrivedを渡すことは可能です。CTRPとして稀に使われます。

以下の方法でそこそこ近いことができていると思います。std::findはC++20で導入されたconstexprのものが必要です。(現時点ではclangはまだ対応していないようです。g++はコンパイルできました。)

C++

1#include <algorithm> 2#include <initializer_list> 3 4enum class Graphic { A, B, C }; 5 6struct ResourceManager 7{ 8 static void preload(std::initializer_list<Graphic> const&) { } 9 static int getGraphicHandle(Graphic) { return 0; } 10}; 11 12template<class U> 13class Base { 14protected: 15 // template <typename U = decltype(this)> 16 // U は Derived になって欲しい… 17 Base() { 18 ResourceManager::preload(U::Graphics); 19 } 20 template<Graphic graphic> 21 void DrawGraphic() { 22 static_assert(U::Graphics.end() != std::find(U::Graphics.begin(), U::Graphics.end(), graphic), ""); 23 Draw(ResourceManager::getGraphicHandle(graphic)); 24 } 25 void Draw(int) { } 26}; 27class Derived : Base<Derived> { 28public: 29 static constexpr auto Graphics = {Graphic::B, Graphic::C}; 30 void exec() { 31 DrawGraphic<Graphic::A>(); // コンパイル時エラーにしたい 32 DrawGraphic<Graphic::B>(); // 成功 33 DrawGraphic<Graphic::C>(); // 成功 34 } 35}; 36 37int main() 38{ 39 Derived derived; 40 derived.exec(); 41}

wandbox

たぶん、コンセプトを使えばもう少し適切なエラー・メッセージを出力させることができるとは思うのですが、力及ばず。

投稿2020/06/23 18:34

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tails

2020/06/29 23:58 編集

テンプレートクラスにするとオブジェクトを Base で持っておいて、他の関数を呼び出したいときに困るかなと思っていましたが、インタフェースを作ればよかったことに気付き、この手法で解決しました。 ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問