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

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

新規登録して質問してみよう
ただいま回答率
85.49%
C++

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

Q&A

解決済

4回答

885閲覧

protected の挙動が分からない

YUKI_DAYO

総合スコア19

C++

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

1グッド

1クリップ

投稿2022/07/21 16:09

地雷質問なのは理解した上で質問させていただきます。
言語仕様の話になると思うのですが、下記のコードの func2 がエラーになるのはおかしくないですか?
B は A を継承しているのですから別オブジェクトの protected を触れても問題ないと思うのですが。
どういう意図でこのような設計になっているのでしょうか?

C++

1class A { 2protected: 3 void call() { } 4}; 5 6class B : public A { 7public: 8 void func1() { call(); } // <- ok 9 void func2(A a) { a.call(); } // <- ng 10 void func3(B b) { b.call(); } // <- ok 11private: 12 void call() { } 13};

c# - Is there a way to reach a protected member of another object from a derived type? - Stack Overflow
C# ですが stack overflow で同様の質問がされており、出来ない旨と出来なくなっている理由(変更されたくない protected の内容を持ったクラスを継承してそこから呼び出すことで変更できてしまう)が書かれていますが、本当にそのためだけにアクセス不可にしているのでしょうか?
また、これを回避する方法があれば教えていただきたいです。お願いいたします。

yohhoy👍を押しています

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

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

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

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

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

fana

2022/07/22 00:59

> どういう意図でこのような設計になっているのでしょうか? これに関しては,実際にその設計を行った誰かに対して問うか,あるいはその誰かがそのような話を記した文面か何かを探す以外には,解は得られないと思います. (そういう情報源無しに「こうだからじゃないの?」って言っても意味ないでしょうし) > これを回避する方法があれば教えていただきたいです。 こっちに関しては,「代替策は無いか?」という話なのでしょうから,どうしてそんな不自然なアクセスをしたくなっちゃったのか? 本当は何をしたいのか? etc... といったような具体的な話を述べた方が良いのではないでしょうか.
YUKI_DAYO

2022/07/22 09:24 編集

> どうしてそんな不自然なアクセスをしたくなっちゃったのか? に関してですが、確かに設計が変な感じになっているのは否めないです。 既に素晴らしい回答を沢山いただいたので、実際の設計に関しての質問は新たに立ち上げようと思います。 追記:質問を立ち上げる前に回答などを参考に設計を考え直してみます。
guest

回答4

0

ベストアンサー

protected と指定されたメンバは派生クラスのオブジェクト・ポインタ・参照を経由してのみ public と同等にアクセスできるというルールです。 (暗黙に this を経由する場合も含みます。)

このルールが制定された理由は「注解 C++リファレンスマニュアル」の 11.5 に書かれているのでそのまま引用します。 この文で「限定公開メンバ」と呼ばれているものは protected 指定されたメンバのことです。

派生クラスが、その基底クラスのどらかの型を持つオブジェクトの限定公開メンバへアクセスできると考えよう。 すなわち、限定公開メンバへのアクセスは、派生クラスのオブジェクト、それへのポインタ、あるいはそれへのリファレンスを経由する必要があるという制限を緩めてみよう。 この結果、クラスは、明示的なキャストを使わずに、無関係のクラスの規定クラス部分へ (それがあたかも自分自身のものであるかのゆに) アクセスできることになっただろう。 これは言語だけが許せるアクセスだったはずである。

この制限が、思いがけない事故に対抗する保護を与えていることを示す例はいくつか存在する。 たとえば、私が銀行を経営していて、多くの違う口座を持っているとしよう。 私は、すべての種類の口座に対する共通の情報を持つ規定クラス Account を持ちたい。 このクラスは、口座の残高の情報も含んでいる。 ゆえに、 Account のフレンドは、すべての Account から構成されるリストを調べることや、私の銀行の財政状態を渡しにいつでも知らせることができる。 しかしながら、各種類の Account は、それぞれ自分自身で、残高の更新のための規則 (たとえば、支払われる利息率はいくらか、利子はいつ複利計算されるのか、受理可能な預金の種類は何か、早期回収に対する特別のペナルティ等) を持っている。 したがって、異種類の口座である派生クラスは全て、残高を更新するためのコードを持つ。 しかしながら、 checking_accounts のメンバ関数は、 AutoLoan_account 中の預金残高にアクセスできないほうがよい。 この制限は、この種のアクセスを防げる。

この制限を取り入れた例は、デバイス・ドライバに関係するものと論理的に等価である。 ある種のデバイスに対するドライバは、ほかの種類のデバイスと関係している情報を偶然に更新し、その結果、システムは不思議な具合にクラッシュしてしまった。 偶然更新された情報は、共通の基底クラスへ格納されていて、適切な派生型ではなく、その基底クラスへのポインタを経由してアクセスされていた。 もちろん更新を行うための仮想関数を使用していたら、この問題は避けられていただろう。

この本は (C++ の設計が委員会主導になる前の) 設計者自身によるものなので設計意図が表れている文章だと思います。

要するに派生クラスから全面的にアクセスできるようにするのは寛容すぎるということです。 派生クラスだけに許したいアクセスを (protected 以外の方法で) コントロールしようとすると煩雑すぎて管理不能になるという Mark Linton の主張が元で protected は導入されており、煩雑なインターフェイスを設計するためにバグが入り込むくらいなら緩いアクセス制限があったほうがマシだという判断がありました。 しかし、制限のための指定なのに緩すぎてもろくなことにならないのも自明なので典型的に必要な制限としてこのへんが落としどころだったということなのでしょう。


想定しているデザインが悪いのでどうにかする言語機能を探しても糊塗にしかならない (美しく解決するなら根本的な見直しが必要) ということはお断りしておきますが、 protected のルールを活用するという前提であればとにかく「派生クラスを経由する」ということを満たしさえすればよく、以下の例で示すような方法を取れなくはないですね。

cpp

1class A { 2protected: 3 void call() { } 4}; 5 6class B : public A { 7 struct C : private A { 8 C(const A& x) : A(x) {} 9 using A::call; 10 }; 11public: 12 void func(C a) { a.call(); } 13}; 14 15int main(void) { 16 A foo; 17 B bar; 18 bar.func(foo); 19}

投稿2022/07/22 05:37

編集2022/07/23 01:28
SaitoAtsushi

総合スコア5444

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

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

YUKI_DAYO

2022/07/22 09:08

回答ありがとうございます。 なるほどこのような経緯があったのですね。 とても面白かったです!勉強になりました!!! それにしても言語設計って本当に大変そうですね...
guest

0

これを回避する方法があれば

何がしたいのかわからないけど,
class A の実装を弄ってもよいのであれば,class A にそれ用のメソッドを仕込めばよいのでは.

C++

1class A { 2protected: 3 void func2_( A a ){ a.call(); } //←コレを仕込む 4protected: 5 void call() { std::cout << "A::call()\n"; } 6}; 7 8class B : public A { 9public: 10 void func1() { call(); } // <- ok 11 void func2(A a){ func2_(a); } //←仕込まれたものを使う 12 void func3(B b) { b.call(); } // <- ok 13private: 14 void call() { std::cout << "B::call()\n"; } 15};

投稿2022/07/22 04:36

編集2022/07/22 04:39
fana

総合スコア11645

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

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

YUKI_DAYO

2022/07/22 09:22

回答ありがとうございます。 @Serbonis さんの回答同様、あまり美しくないですよね...
fana

2022/07/22 10:01 編集

言語仕様でできないことを何とか捻じ曲げようとしている事自体が,おそらくは「美しく」はないものであろうと思うので,その実現策を「美しく」用意しろ,ってのは無理難題ではなかろうか? (「美しくない」を批判だとか受け取っているわけじゃないよ.ただ純粋に「美しい抜け道」って無理じゃね? っていう)
guest

0

callを呼び出すラッパーを静的メンバ関数で用意する方法はどうでしよう。不特定多数の派生クラスを意識しなくてよくなります。

C++

1class A { 2protected: 3 void call() { } 4 static void call(A a) { a.call(a);} 5}; 6 7class B : public A { 8public: 9 void func2(A a) { a.call(a); } 10};

投稿2022/07/22 04:35

編集2022/07/22 04:37
Serbonis

総合スコア581

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

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

YUKI_DAYO

2022/07/22 09:21

回答ありがとうございます。 分かりやすくていいとは思うのですが、あまり美しくはない(個人の感想です)ですよね汗
guest

0

これを回避する方法があれば教えていただきたいです。

「特定のクラスにのみprivate/protectedへのアクセスを許したい」
なら friend が使えます。

C++

1#include <iostream> 2 3class A { 4 friend class B; 5protected: 6 void call() { std::cout << "A::call()\n"; } 7}; 8 9class B { 10public: 11 void funcB() { call(); } // <- ok 12 void funcA(A a) { a.call(); } 13private: 14 void call() { std::cout << "B::call()\n"; } 15}; 16 17int main() { 18 A a; B b; 19 b.funcA(a); 20 b.funcB(); 21}

投稿2022/07/21 23:51

編集2022/07/22 00:00
episteme

総合スコア16614

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

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

YUKI_DAYO

2022/07/22 09:18

回答ありがとうございます。 そういえば friend とかありましたね。 完全に忘れていました。 使ったことのない機能なので調べてきましたが、依存関係がかなり複雑になりそうですね...
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.49%

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

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

質問する

関連した質問