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

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

新規登録して質問してみよう
ただいま回答率
85.48%
ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

C++

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

Q&A

解決済

5回答

5771閲覧

C++ 別クラスの関数ポインタを仮引数に持った関数を定義し使用する方法を知りたい

Kchan_01

総合スコア110

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

C++

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

0グッド

0クリップ

投稿2021/07/05 08:33

編集2021/07/05 10:04

やりたいこと

C言語をそこそこ学んだ後、C++を学び始めたものです。

C++の勉強として、電話帳の動きを模したプログラムを作成しています。
重複した処理が多いため、共通部分を抽象化したいです。
関数ポインタを渡すことで、きれいに書けると思っていますがうまくいきません。

クラス定義

cpp

1class Contact 2{ 3 public: 4 void setFirstName(std::string s) { firstName = s; } 5 void setLastName(std::string s) { lastName = s; } 6 void setNickname(std::string s) { nickname = s; } 7 void setphoneNumber(std::string s) { phoneNumber = s; } 8 void setDarkestSecret(std::string s) { darkestSecret = s; } 9 10 std::string getFirstName() const { return firstName; } 11 std::string getLastName() const { return lastName; } 12 std::string getNickname() const { return nickname; } 13 std::string getPhoneNumber() const { return phoneNumber; } 14 std::string getDarkestSecret() const { return darkestSecret; } 15 private: 16 std::string firstName; 17 std::string lastName; 18 std::string nickname; 19 std::string phoneNumber; 20 std::string darkestSecret; 21}; 22 23class Phonebook 24{ 25 public: 26 void add(int i); 27 void exit(); 28 29 private: 30 Contact contact[8]; 31};

クラス関数の定義

ここの繰り返し処理を関数ポインタを使うことで短く書きたいです。

cpp

1void Phonebook::add(int i) 2{ 3 std::string line; 4 5 // first nameの入力 6 std::cout << "first name : "; 7 std::getline(std::cin, line); 8 while (line.empty()) 9 { 10 std::cout << "first name : "; 11 std::getline(std::cin, line); 12 } 13 contact[i].setFirstName(line); 14 15 // last nameの入力 16 std::cout << "last name : "; 17 std::getline(std::cin, line); 18 while (line.empty()) 19 { 20 std::cout << "last name : "; 21 std::getline(std::cin, line); 22 } 23 contact[i].setLastName(line); 24}

試したこと

追記 2021/07/05 17:59

以下の関数を定義し、呼び出してみましたが、うまく行きません。

cpp

1void addItem(int i, void (Contact::*fn)(std::string), std::string s) 2{ 3 std::string line; 4 5 std::cout << s; 6 std::getline(std::cin, line); 7 while (line.empty()) 8 { 9 std::cout << s; 10 std::getline(std::cin, line); 11 } 12 fn(line); 13} 14 15void Phonebook::add(int i) 16{ 17 Contact tmp = getContact(i); 18 addItem(i, Contact::setFirstName, "frist name : "); 19 20}

このエラーが出ます。

terminal

1❯ g++ main.cpp sample.cpp 2sample.cpp:20:7: error: called object type 'void (Contact::*)(std::string)' is not a function or function pointer 3 fn(line); 4 ~~^ 5sample.cpp:26:25: error: call to non-static member function without an object argument 6 addItem(i, Contact::setFirstName, "frist name : "); 7 ~~~~~~~~~^~~~~~~~~~~~ 82 errors generated.

よろしくお願いします。

環境

C++11

❯ g++ --version Configured with: --prefix=/Library/Developer/CommandLineTools/usr --with-gxx-include-dir=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/4.2.1 Apple clang version 12.0.5 (clang-1205.0.22.11) Target: x86_64-apple-darwin20.5.0 Thread model: posix InstalledDir: /Library/Developer/CommandLineTools/usr/bin ## コンパイル方法 ❯ g++ main.cpp sample.cpp

追記 2021/07/05 19:00

templateを使わない縛りで書いているので、std::function<void(std::string)>を使用して実現したいです。

cpp

1void Phonebook::addItem(int i, std::function<void(std::string)> fn, std::string s) 2{ 3 std::string line; 4 5 std::cout << s; 6 std::getline(std::cin, line); 7 while (line.empty()) 8 { 9 std::cout << s; 10 std::getline(std::cin, line); 11 } 12 fn(line); 13} 14 15void Phonebook::add(int i) 16{ 17 addItem(i, &getContact(i).setFirstName, "frist name : "); 18}

以下のエラーが出ます。

terminal

1❯ g++ -std=c++11 main.cpp sample.cpp && ./a.out 2sample.cpp:24:16: error: cannot create a non-constant pointer to member function 3 addItem(i, &getContact(i).setFirstName, "frist name : "); 4 ^~~~~~~~~~~~~~~~~~~~~~~~~~~ 51 error generated.

&を取ってもエラーが出て実行できませんでした。

terminal

1❯ g++ -std=c++11 main.cpp sample.cpp && ./a.out 2sample.cpp:24:30: error: reference to non-static member function must be called 3 addItem(i, getContact(i).setFirstName, "frist name : "); 4 ~~~~~~~~~~~~~~^~~~~~~~~~~~ 51 error generated.

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

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

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

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

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

fana

2021/07/05 09:53

正直,何がしたいのかがわからない. > ここの繰り返し処理を関数ポインタを使うことで短く書きたいです という話の「繰り返し処理」とはwhileループではないかと思うのだが, > 以下の関数を定義し、呼び出してみましたが、うまく行きません。 という話の関数内ではwhileループがそのまま残っており,何も短くなっていないように見える. --- 「共通部分」というのが入力のためのwhileループのことなのであれば,その処理を private メンバ関数にでも切り出すだけの話ではないのか? 「関数ポインタ」が出てくる話の流れがわからない.
Kchan_01

2021/07/05 10:42

確かに関数ポインタを使わなくても短く書けました。 void Phonebook::add(int i) { contact[i].setFirstName(this->ittText("first name : ")); contact[i].setLastName(this->ittText("last name : ")); contact[i].setNickname(this->ittText("nickname : ")); contact[i].setphoneNumber(this->ittText("phone number : ")); contact[i].setDarkestSecret(this->ittText("darkest secret : ")); } 関数ポインタを使用した書き方も理解しておきたいので、質問内容は変更せずに記載しておきます。 ご指摘ありがとうございます。
guest

回答5

0

templateを使わない縛りで書いているので、std::function<void(std::string)>を使用して実現したいです。

まず理解しなければならないことは、non-static member functionには暗黙の引数が存在するということです。これは関数呼び出し規約の段階で解決されているため普段意識することはありません。しかし思い出してください。メンバ関数はthisポインタを使ってメンバ変数にアクセスできますよね?じゃあそのthisポインタ、どこから湧いてきたの?

そう、thisポインタこそが暗黙の引数であるのです。

cpp

1struct foo { 2 int aaa; 3 void bar() { this->aaa = 3; } 4};

例えばこのfoo::bar()は一見引数を取らないように見えますが、暗黙的に0番目の引数とでも呼ぶべきもの、つまりthisを受け取っています。

つまり、メンバー関数を通常の関数のように扱うためにはまず、この暗黙の引数thisと紐付けばければなりません。

方法は大きく2通りあります。

1つ目はmatukesoさんの回答にあるようにlambdaを使う方法です。lambda式はcaptureによって任意の変数を使うことができます。thisも例外ではありません。

cpp

1[this](std::string s){ getContact(i).setFirstName(s); }

ここでは明示的にthisポインタをコピーキャプチャしましたが、別に=にしても構いません。こうすることでthisとの紐付けはlambda式がやってくれるので、lambda式で定義した関数オブジェクトを呼び出す側はthisについて感知する必要はなくなります。なお今回は関係ないですが、非同期処理などの場面でthisポインタをコピーキャプチャすると寿命が尽きてしまっている可能性があるので*thisをキャプチャするとかshared_ptrにするとかして回避する必要があります。

2つ目はstd::bindを使う方法です。ただし今回は単純なメンバ関数呼び出しじゃないようなので使いにくいでしょう。

cpp

1struct foo { 2 int aaa; 3 void bar() { this->aaa = 3; } 4}; 5foo ff{}; 6std::function<void()> f = std::bind(foo::bar, ff); 7f();

投稿2021/07/05 23:37

yumetodo

総合スコア5850

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

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

0

ベストアンサー

こういうことがしたいのでしょうか?

C++

1class Cont 2{ 3public: 4 Cont( const std::string &Name ) : m_Name(Name) {} 5public: 6 void SetXXX( const std::string &S ){ std::cout << m_Name + ".SetXXX : " + S << std::endl; } 7 void SetYYY( const std::string &S ){ std::cout << m_Name + ".SetYYY : " + S << std::endl; } 8private: 9 std::string m_Name; 10}; 11 12class Book 13{ 14public: 15 //SetXXX() と SetYYY() は共通実装 SetCommon() を使用する 16 void SetXXX( int ContIndex, const std::string &S ){ SetCommon( ContIndex, S, &Cont::SetXXX ); } 17 void SetYYY( int ContIndex, const std::string &S ){ SetCommon( ContIndex, S, &Cont::SetYYY ); } 18 19private: 20 //共通実装 21 void SetCommon( int ContIndex, const std::string &S, void (Cont::*fn)( const std::string & ) ) 22 { ( m_Conts[ ContIndex ].*fn )( S ); } 23 24private: 25 Cont m_Conts[2]{ std::string("Cont0") ,std::string("Cont1") }; 26};

投稿2021/07/05 11:02

fana

総合スコア11658

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

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

fana

2021/07/05 11:05

メンバ関数ポインタの呼び出しの文法などはググると見つかるかと. .* とか ->* とかいう書き方をするんだけど,こいつが引数を書くための () よりも優先度が低いので, m_Conts[ ContIndex ].*fn( S ); だとダメ. なので,提示したコードのように,()を付けて ( m_Conts[ ContIndex ].*fn )( S ); とする.
Kchan_01

2021/07/06 04:51

以下のように書いて、実現できました。ありがとうございます! void Phonebook::addItem(int i, void (Contact::*fn)(std::string), std::string s) { std::string line; std::cout << s; std::getline(std::cin, line); while (line.empty()) { std::cout << s; std::getline(std::cin, line); } (contact[i].*fn)(line); } void Phonebook::add(int i) { this->addItem(i, &Contact::setFirstName ,"first name : "); this->addItem(i, &Contact::setLastName ,"last name : "); this->addItem(i, &Contact::setNickname ,"nickname : "); this->addItem(i, &Contact::setphoneNumber ,"phone number : "); this->addItem(i, &Contact::setDarkestSecret ,"darkest secret : "); }
guest

0

メンバ関数ポインタは使うのも面倒なので、thisをbindしたラムダを渡いたほうがいいのでは。
具体的にその構造なら、addItemはtemplate関数にする。
templateがいやなら、std::function<void(std::string)>とかを引数にもできる。

C++

1template<class Fn> 2void addItem(int i, Fn fn, std::string s){ ... } 3 4void Phonebook::add(int i) 5{ 6 addItem(i, [=](std::string s){ getContact(i).setFirstName(s); }, "frist name : "); 7}

投稿2021/07/05 09:30

matukeso

総合スコア1590

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

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

0

コメントだとマークダウンが機能しないので、こちらにも書いておきます。

@fanaさんの回答をもとに以下のように書いて実現することができました。

cpp

1void Phonebook::addItem(int i, void (Contact::*fn)(std::string), std::string s) 2{ 3 std::string line; 4 5 std::cout << s; 6 std::getline(std::cin, line); 7 while (line.empty()) 8 { 9 std::cout << s; 10 std::getline(std::cin, line); 11 } 12 (contact[i].*fn)(line); 13} 14 15void Phonebook::add(int i) 16{ 17 this->addItem(i, &Contact::setFirstName ,"first name : "); 18 this->addItem(i, &Contact::setLastName ,"last name : "); 19 this->addItem(i, &Contact::setNickname ,"nickname : "); 20 this->addItem(i, &Contact::setphoneNumber ,"phone number : "); 21 this->addItem(i, &Contact::setDarkestSecret ,"darkest secret : "); 22}

投稿2021/07/06 04:52

Kchan_01

総合スコア110

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

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

0

別クラスで定義されている関数を関数ポインタとして使用することはできないのでしょうか。

staticでない)メンバ関数へのポインタは、普通の関数ポインタとしてでなく、それを明示した型でないと成立しません(参考)。

投稿2021/07/05 08:37

maisumakun

総合スコア145184

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

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

maisumakun

2021/07/05 09:16

> 追記 2021/07/05 17:59 参考URLの通り、メンバ関数へのポインタを「与える」側でも「呼ぶ」側でも、それぞれに応じた特殊な書き方が必要です。
Kchan_01

2021/07/05 09:21

明示的に記載してもエラーがでます。明示的とは、どのような記述方法を指しているのでしょうか。 ```cpp void addItem(int i, void (Contact::*fn)(std::string), std::string s) { std::string line; std::cout << s; std::getline(std::cin, line); while (line.empty()) { std::cout << s; std::getline(std::cin, line); } fn(line); } void Phonebook::add(int i) { Contact tmp = getContact(i); addItem(i, Contact::setFirstName, "frist name : "); } ``` このエラーが出ます。 ```terminal ❯ g++ main.cpp sample.cpp sample.cpp:20:7: error: called object type 'void (Contact::*)(std::string)' is not a function or function pointer fn(line); ~~^ sample.cpp:26:25: error: call to non-static member function without an object argument addItem(i, Contact::setFirstName, "frist name : "); ~~~~~~~~~^~~~~~~~~~~~ 2 errors generated. ```
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問