Item 11:private undefined functionよりdeleted functionを優先
本文は《effective modern C++》から翻訳して、レベルが限られているため、翻訳が完全に正しいことを保証することができなくて、間違いを指摘することを歓迎します.ありがとう!
他の開発者にコードを提供し、特定の関数を呼び出すのを阻止したい場合は、通常この関数を宣言しません.関数を宣言しないと、関数は呼び出されません.簡単すぎる!しかし、C++は関数を宣言し、お客様がこれらの関数を呼び出すのを阻止したい場合は、簡単なことは簡単ではありません.
この場合、「特殊なメンバー関数」にのみ発生します.つまり、これらのメンバー関数が必要な場合、C++が自動的に生成します.Item 17はこれらの関数を詳細に論じたが,現在ではcopy構造関数とcopy assignment operatorのみを考慮している.この章では、C++98でよく使われる方法をC++11のより良い方法で置き換えることを主に説明します.そしてC++98で最も抑制したいメンバー関数は、copyコンストラクション関数、assignment operator、または抑制したい場合が多いです.
C++98では、これらの関数をブロックする方法は、privateとして宣言し、定義しないことです.例を挙げると、C++標準ライブラリのiostreamクラス階層の最下位にclass templateがbasic_と呼ばれています.ios.すべてのistreamとostreamは、このクラスから継承されます(直接ではないかもしれません).コピーistreamとostreamは人気がありません.これらの操作が何をすべきかを明確な概念で規定していないからです.例を挙げると、istreamオブジェクトは入力値のストリームを表し、一部の値はすでに読み込まれており、一部の値は後で読み込まれる可能性があります.istreamがコピーされた場合、前に読んだ値と後で読む値をすべてコピーする必要がありますか?この問題を処理する最も簡単な方法は、存在しないと定義することです.それをするにはstreamのコピーを禁止すればいいだけです.
istreamクラスとostreamクラスをコピーできないようにbasic_iosはC++98でこのように実現される(注釈を含む):
これらの関数をprivateと宣言すると、お客様が呼び出すのを阻止できます.意図的に定義しないということは、いくつかのコードがアクセス(つまり、メンバー関数またはメタクラス)して使用する権利がある場合、リンクすると、関数定義が見つからずに失敗することを意味します.
C++11では、copyコンストラクション関数とcopy assignment operatorをタグ付けし、「=delete」を使用してdeleted関数にするより良い方法があります.ここではC++11のbasic_を示すiosの実装:
これらの関数を「削除」することとprivateと宣言することの違いは、よりスタイリッシュに見える以外にありませんが、ここには実質的なメリットがあります.deleted関数はどのようにも使用されないので、メンバー関数と友元関数でbasic_をコピーしようとするとiosオブジェクトも失敗します.C++98(このようなエラーはリンク前に診断できない)よりも、これはアップグレードです.
従来、deleted関数はprivateではなくpublicとして宣言されています.これには理由がある.顧客コードがメンバー関数を使用しようとすると、C++はdeletedステータスをチェックする前にアクセス性をチェックします.クライアント・コードがdeleted private関数を使用しようとすると、関数のアクセス性は使用できるかどうかに影響しませんが(この関数は常に呼び出せません)、一部のコンパイラは関数がprivateであることに「文句」を言うだけです.履歴に残っているコードを変更してprivate-and-not-definedメンバー関数をdeleted関数に置き換えると、特にこの点(deleted関数をpublicと宣言する)を覚えておいてください.新しい関数をpublicにすると、より良いエラーメッセージが発生します.
関数をprivateとして宣言しなければならないよりも、deleted関数には重要な利点があります.それは、どの関数もdeletedになることができます.例を挙げると、整形をパラメータとし、boolがラッキーな数字であるかどうかを示す非メンバー関数があるとします.
C++はCから継承されます.これは、多くの他のタイプが数値タイプと曖昧に見なされ、intに暗黙的に変換されることを意味しますが、コンパイルされた呼び出しによっては意味がありません.
ラッキーな数字が整形タイプでなければならない場合は、上記の呼び出しを阻止することができます.
1つの方法は、フィルタリングしたいタイプでdeletedリロードを作成することです.
(不思議に思うかもしれませんが、doubleリロードバージョンのコメントではdoubleもfloatも拒否されています.floatからintおよびfloatからdoubleへの変換を与えると、C++がfloatをdoubleに優先的に変換し、疑問は消えます.そのため、floatでisLuckyを呼び出すとintバージョンのリロードではなくdoubleバージョンが呼び出されます.はい、それ(コンパイラ)isLuckyを呼び出してみますが、実際にはこのバージョンのリロードはdeletedなので、コンパイル時にこの呼び出しをブロックします.)
deleted関数は使用できませんが、プログラムの一部です.したがって,それらは解析を再ロードする際に考慮される.これは、上記のようなdeleted関数宣言式を使用すると、嫌な呼び出しが拒否される理由です.
deleted関数には、不要なtemplateインスタンスをブロックするテクニックもあります(privateメンバー関数ではできません).たとえば、built-inポインタを使用するtemplateが必要だとします(第4章では、rawポインタよりもスマートポインタを優先することをお勧めします):
ポインタの世界には、2つの特殊な状況があります.1つは
これは、インスタンスを削除するだけで簡単に実行できます.
ここで、
そして、本当にやりたいことは、
興味深いことに、関数templateがclassに組み込まれている場合、特定のインスタンスをprivate(ああ、典型的なC++98の方法)として宣言することで無効にしたい場合は、メンバー関数templateを異なるアクセスレベル(プライマリtemplateのアクセスレベルとは異なる)に特化することはできません.たとえば、processPointerがWidgetに組み込まれたメンバー関数templateである場合、void*ポインタの呼び出しを無効にしたい場合は、コンパイルできませんが、C++98の方法は次のように見えます.
問題はtemplate特化がクラスの役割ドメインではなくネーミングスペースの役割ドメインに書かなければならないことです.この問題はdeleted関数には影響しません.異なるアクセスレベルが必要ないからです.classの外で削除できます(したがって、ネーミングスペースの役割ドメインにあります):
実際、C++98では、宣言関数がprivateであることは、C++11のdelted関数を実装しようとすることによって実現されるものではない.模倣として,C++98の手法は実物(C++11のdeleted関数)にそっくりではなかった.classの外では機能しません.classの中では常に機能しません.機能しても、リンク前に機能しない可能性があります.だからdeleted関数を使い続けよう!!!
君が覚えていること private undefined functionよりdeleted function を優先的に使用非メンバー関数およびtemplateインスタンス化関数を含む任意の関数を削除できます.
他の開発者にコードを提供し、特定の関数を呼び出すのを阻止したい場合は、通常この関数を宣言しません.関数を宣言しないと、関数は呼び出されません.簡単すぎる!しかし、C++は関数を宣言し、お客様がこれらの関数を呼び出すのを阻止したい場合は、簡単なことは簡単ではありません.
この場合、「特殊なメンバー関数」にのみ発生します.つまり、これらのメンバー関数が必要な場合、C++が自動的に生成します.Item 17はこれらの関数を詳細に論じたが,現在ではcopy構造関数とcopy assignment operatorのみを考慮している.この章では、C++98でよく使われる方法をC++11のより良い方法で置き換えることを主に説明します.そしてC++98で最も抑制したいメンバー関数は、copyコンストラクション関数、assignment operator、または抑制したい場合が多いです.
C++98では、これらの関数をブロックする方法は、privateとして宣言し、定義しないことです.例を挙げると、C++標準ライブラリのiostreamクラス階層の最下位にclass templateがbasic_と呼ばれています.ios.すべてのistreamとostreamは、このクラスから継承されます(直接ではないかもしれません).コピーistreamとostreamは人気がありません.これらの操作が何をすべきかを明確な概念で規定していないからです.例を挙げると、istreamオブジェクトは入力値のストリームを表し、一部の値はすでに読み込まれており、一部の値は後で読み込まれる可能性があります.istreamがコピーされた場合、前に読んだ値と後で読む値をすべてコピーする必要がありますか?この問題を処理する最も簡単な方法は、存在しないと定義することです.それをするにはstreamのコピーを禁止すればいいだけです.
istreamクラスとostreamクラスをコピーできないようにbasic_iosはC++98でこのように実現される(注釈を含む):
template<class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base{
public:
...
public:
basic_ios(const basic_ios&); // not defined
basic_ios& operator=(const basic_ios&); //not defined
};
これらの関数をprivateと宣言すると、お客様が呼び出すのを阻止できます.意図的に定義しないということは、いくつかのコードがアクセス(つまり、メンバー関数またはメタクラス)して使用する権利がある場合、リンクすると、関数定義が見つからずに失敗することを意味します.
C++11では、copyコンストラクション関数とcopy assignment operatorをタグ付けし、「=delete」を使用してdeleted関数にするより良い方法があります.ここではC++11のbasic_を示すiosの実装:
template<calss charT, class trais = char_traits<charT> >
class basi_ios : public ios_base{
public:
...
basic_ios(const basic_ios&) = delete;
basic_ios& operator= (const basic_ios&) = delete;
...
};
これらの関数を「削除」することとprivateと宣言することの違いは、よりスタイリッシュに見える以外にありませんが、ここには実質的なメリットがあります.deleted関数はどのようにも使用されないので、メンバー関数と友元関数でbasic_をコピーしようとするとiosオブジェクトも失敗します.C++98(このようなエラーはリンク前に診断できない)よりも、これはアップグレードです.
従来、deleted関数はprivateではなくpublicとして宣言されています.これには理由がある.顧客コードがメンバー関数を使用しようとすると、C++はdeletedステータスをチェックする前にアクセス性をチェックします.クライアント・コードがdeleted private関数を使用しようとすると、関数のアクセス性は使用できるかどうかに影響しませんが(この関数は常に呼び出せません)、一部のコンパイラは関数がprivateであることに「文句」を言うだけです.履歴に残っているコードを変更してprivate-and-not-definedメンバー関数をdeleted関数に置き換えると、特にこの点(deleted関数をpublicと宣言する)を覚えておいてください.新しい関数をpublicにすると、より良いエラーメッセージが発生します.
関数をprivateとして宣言しなければならないよりも、deleted関数には重要な利点があります.それは、どの関数もdeletedになることができます.例を挙げると、整形をパラメータとし、boolがラッキーな数字であるかどうかを示す非メンバー関数があるとします.
bool isLucky(int number);
C++はCから継承されます.これは、多くの他のタイプが数値タイプと曖昧に見なされ、intに暗黙的に変換されることを意味しますが、コンパイルされた呼び出しによっては意味がありません.
if(isLucky('a')) ... //'a' ?
if(isLucky(true))... //"true" ?
if(isLucky(3.5))... // ,
// 3
ラッキーな数字が整形タイプでなければならない場合は、上記の呼び出しを阻止することができます.
1つの方法は、フィルタリングしたいタイプでdeletedリロードを作成することです.
bool isLucky(int number);
isLucky(char) = delete; // char
isLucky(bool) = delete; // bool
isLucky(double) = delete; // double float
(不思議に思うかもしれませんが、doubleリロードバージョンのコメントではdoubleもfloatも拒否されています.floatからintおよびfloatからdoubleへの変換を与えると、C++がfloatをdoubleに優先的に変換し、疑問は消えます.そのため、floatでisLuckyを呼び出すとintバージョンのリロードではなくdoubleバージョンが呼び出されます.はい、それ(コンパイラ)isLuckyを呼び出してみますが、実際にはこのバージョンのリロードはdeletedなので、コンパイル時にこの呼び出しをブロックします.)
deleted関数は使用できませんが、プログラムの一部です.したがって,それらは解析を再ロードする際に考慮される.これは、上記のようなdeleted関数宣言式を使用すると、嫌な呼び出しが拒否される理由です.
if(isLucky('a')) ... // , deleted
if(isLucky(true))... //
if(isLucky(3.5))... //
deleted関数には、不要なtemplateインスタンスをブロックするテクニックもあります(privateメンバー関数ではできません).たとえば、built-inポインタを使用するtemplateが必要だとします(第4章では、rawポインタよりもスマートポインタを優先することをお勧めします):
template<typename T>
void processPointer(T* ptr);
ポインタの世界には、2つの特殊な状況があります.1つは
void*
ポインタです.彼らは参照を解くことができず、増加したり減少したりすることができません.もう1つはchar*
ポインタです.単一の文字を指すポインタではなく、Cスタイルの文字列を指すポインタを表すためによく使われています.これらの特殊な状況はしばしば特別な処理を必要とする.ここで、processPointer templateでは、これらのタイプの呼び出しを拒否するために特別な処理が必要であると仮定します.つまり、void*
またはchar*
ポインタを使用してprocessPointerを呼び出すことはできません.これは、インスタンスを削除するだけで簡単に実行できます.
template<>
void processPointer<void>(void*) = delete;
template<>
void processPointer<char>(char*) = delete;
ここで、
void*
またはchar*
でprocessPointerを呼び出すのは無効です.const void*
およびconst char*
も無効である必要があります.したがって、これらのインスタンスも削除する必要があります.template<>
void processPointer<const void>(const void*) = delete;
template<>
void processPointer<const char>(const char*) = delete;
そして、本当にやりたいことは、
const volatile void*
とconst volatile char*
のリロードを削除し、他の標準文字タイプ(std::wchar_t,std::char 16_tおよびstd::char 32_t)のためにこのような仕事をする必要があります.興味深いことに、関数templateがclassに組み込まれている場合、特定のインスタンスをprivate(ああ、典型的なC++98の方法)として宣言することで無効にしたい場合は、メンバー関数templateを異なるアクセスレベル(プライマリtemplateのアクセスレベルとは異なる)に特化することはできません.たとえば、processPointerがWidgetに組み込まれたメンバー関数templateである場合、void*ポインタの呼び出しを無効にしたい場合は、コンパイルできませんが、C++98の方法は次のように見えます.
class Widget{
public:
...
template<typename T>
void processPointer(T* ptr)
{ ... }
private:
template<>
void processPointer<void>(void*); //
};
問題はtemplate特化がクラスの役割ドメインではなくネーミングスペースの役割ドメインに書かなければならないことです.この問題はdeleted関数には影響しません.異なるアクセスレベルが必要ないからです.classの外で削除できます(したがって、ネーミングスペースの役割ドメインにあります):
class Widget{
public:
...
template<typename T>
void processPointer(T* ptr)
{ ... }
...
};
template<>
void Widget::processPointer<void>(void*) = delete;
実際、C++98では、宣言関数がprivateであることは、C++11のdelted関数を実装しようとすることによって実現されるものではない.模倣として,C++98の手法は実物(C++11のdeleted関数)にそっくりではなかった.classの外では機能しません.classの中では常に機能しません.機能しても、リンク前に機能しない可能性があります.だからdeleted関数を使い続けよう!!!
君が覚えていること