Qt wiki will be updated on October 12th 2023 starting at 11:30 AM (EEST) and the maintenance will last around 2-3 hours. During the maintenance the site will be unavailable.

API Design Principles/ja: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
[toc align_right=&quot;yes&amp;quot; depth=&quot;3&amp;quot;]<br />[[Category:Developing Qt::Guidelines]]<br />[[API-Design-Principles|English]] [[API-Design-Principles-Russia|Русский]] '''日本語'''
[toc align_right="yes" depth="3"]
[[Category:Developing Qt::Guidelines]]
[[API-Design-Principles|English]] [[API-Design-Principles-Russia|Русский]] '''日本語'''


= API 設計の原理原則 =
= API 設計の原理原則 =
Line 7: Line 9:
基本的にこのガイドラインはパブリックな API に対するものですが、同様のテクニックをプライベートな API に対しても適用することを推奨しています。
基本的にこのガイドラインはパブリックな API に対するものですが、同様のテクニックをプライベートな API に対しても適用することを推奨しています。


Jasmin Blanchette による &quot;Little Manual of API Design (PDF)&quot;:http://chaos.troll.no/~shausman/api-design/api-design.pdf も合わせて読むと良いでしょう。
Jasmin Blanchette による "Little Manual of API Design (PDF)":http://chaos.troll.no/~shausman/api-design/api-design.pdf も合わせて読むと良いでしょう。


== 良い API の6つの特徴 ==
== 良い API の6つの特徴 ==
Line 13: Line 15:
プログラマーにとって API とは、エンドユーザーにとっての GUI のようなものです。API が人間のプログラマーによって使われるという事実を強調する場合、API の ‘P’ は “Program” ではなく “Programmer” を意味します。
プログラマーにとって API とは、エンドユーザーにとっての GUI のようなものです。API が人間のプログラマーによって使われるという事実を強調する場合、API の ‘P’ は “Program” ではなく “Programmer” を意味します。


&quot;Qt Quarterly 13 の API design に関する記事&amp;quot;:http://doc.qt.nokia.com/qq/qq13-apis.html で、Matthias は API は最小限かつ完全で、明確でシンプルなセマンティックを持ち、直感的で、簡単に覚ることができ、可読なコードを導くべきだという彼の考えを示しました。
"Qt Quarterly 13 の API design に関する記事":http://doc.qt.nokia.com/qq/qq13-apis.html で、Matthias は API は最小限かつ完全で、明確でシンプルなセマンティックを持ち、直感的で、簡単に覚ることができ、可読なコードを導くべきだという彼の考えを示しました。


* '''最小限であれ''': 最小限の API とは、クラス数を可能な限り少なくし、クラスのパブリックなメンバも可能な限り少なくするということです。これにより、理解が容易になり、覚えやすく、デバッグがしやすく、API の変更もしやすくなります。
* '''最小限であれ''': 最小限の API とは、クラス数を可能な限り少なくし、クラスのパブリックなメンバも可能な限り少なくするということです。これにより、理解が容易になり、覚えやすく、デバッグがしやすく、API の変更もしやすくなります。
Line 31: Line 33:
== 静的なポリモーフィズム ==
== 静的なポリモーフィズム ==


同じようなクラスは同じような API を持つべきです。これは、実行時のポリモーフィズムが利用できる場所では、継承により実現することができます。しかし、ポリモーフィズムは設計時にも起こります。例えば QProgressBar の QSlider への変更や QString の QByteArray への変更など、API が似ていることでこれらの変更がとても簡単にできることがわかるでしょう。我々はこれを &quot;静的なポリモーフィズム&amp;quot; と呼んでいます。
同じようなクラスは同じような API を持つべきです。これは、実行時のポリモーフィズムが利用できる場所では、継承により実現することができます。しかし、ポリモーフィズムは設計時にも起こります。例えば QProgressBar の QSlider への変更や QString の QByteArray への変更など、API が似ていることでこれらの変更がとても簡単にできることがわかるでしょう。我々はこれを "静的なポリモーフィズム" と呼んでいます。


静的なポリモーフィズムにより API やプログラミングのパターンを覚えるのも簡単になります。関係するクラス群が同じような API を持つということは、それぞれのクラスが完璧な API をそれぞれ持つよりも結果として良い場合があります。
静的なポリモーフィズムにより API やプログラミングのパターンを覚えるのも簡単になります。関係するクラス群が同じような API を持つということは、それぞれのクラスが完璧な API をそれぞれ持つよりも結果として良い場合があります。
Line 37: Line 39:
Qt ではやむを得ない場合を除き、実際の継承よりもこの静的なポリモーフィズムを採用することを好んでいます。これにより、Qt のパブリックなクラス数を少なく保つことができ、Qt の初心者でもドキュメントを見て使用方法を簡単に理解することができるようになります。
Qt ではやむを得ない場合を除き、実際の継承よりもこの静的なポリモーフィズムを採用することを好んでいます。これにより、Qt のパブリックなクラス数を少なく保つことができ、Qt の初心者でもドキュメントを見て使用方法を簡単に理解することができるようになります。


'''良い例''': QDialogButtonBox と QMessageBox は &quot;QAbstractButtonBox&amp;quot; のようなクラスを継承することなくボタンに関する同じような API (addButton(), setStandardButtons() など)を持ちます。
'''良い例''': QDialogButtonBox と QMessageBox は "QAbstractButtonBox" のようなクラスを継承することなくボタンに関する同じような API (addButton(), setStandardButtons() など)を持ちます。


'''悪い例''': QAbstractSocket は QTcpSocket と QUdpSocket により継承されていますが、この2つのクラスでは動作が大きく異なります。QAbstractSocket のポインタを有効に使用した(できた)例は見たことがありません。
'''悪い例''': QAbstractSocket は QTcpSocket と QUdpSocket により継承されていますが、この2つのクラスでは動作が大きく異なります。QAbstractSocket のポインタを有効に使用した(できた)例は見たことがありません。


'''難しい例''': QBoxLayout は QHBoxLayout と QVBoxLayout の基底クラスです。利点: QBoxLayout を使用し、ツールバーで setOrientation() を呼ぶことで Horizontal/Vertical の設定ができる。欠点: 余計なクラスで、ユーザーは ((QBoxLayout *)hbox)<s>&gt;setOrientation(Qt::Vertical) とする事もできるが、あまり意味がない。
'''難しい例''': QBoxLayout は QHBoxLayout と QVBoxLayout の基底クラスです。利点: QBoxLayout を使用し、ツールバーで setOrientation() を呼ぶことで Horizontal/Vertical の設定ができる。欠点: 余計なクラスで、ユーザーは ((QBoxLayout *)hbox)->setOrientation(Qt::Vertical) とする事もできるが、あまり意味がない。
<br />h2. プロパティに基づく API
 
<br />新しい Qt のクラスは &quot;プロパティに基づく API&amp;quot; を持つ傾向にあります。QTimer を例に見てみましょう。
h2. プロパティに基づく API
<br /><code><br /> QTimer timer;<br /> timer.setInterval(1000);<br /> timer.setSingleShot(true);<br /> timer.start();<br /></code>
 
<br />''プロパティ'' はそのオブジェクトの状態の一部となる概念的な属性を意味します。Q_PROPERTY かどうかはここでは関係ありません。使用可能な場合、プロパティの設定は順序に依存すべきではありません。つまり、個々のプロパティは直交であるべきです。例えば、上記の例は以下のようにも書くことができます。
新しい Qt のクラスは "プロパティに基づく API" を持つ傾向にあります。QTimer を例に見てみましょう。
<br /><code><br /> QTimer timer;<br /> timer.setSingleShot(true);<br /> timer.setInterval(1000);<br /> timer.start();<br /></code>
 
<br />''簡単に'' 以下のように記述することも可能です。
<code>
<br /><code>timer.start(1000)<code>
QTimer timer;
<br />同じことを QRegExp でも見てみましょう。
timer.setInterval(1000);
<br /></code><br /> QRegExp regExp;<br /> regExp.setCaseSensitive(Qt::CaseInsensitive);<br /> regExp.setPattern(&quot;'''.'''&quot;);<br /> regExp.setPatternSyntax(Qt::WildcardSyntax);<br /><code>
timer.setSingleShot(true);
<br />このような API の実装では、内部のオブジェクトが遅延するように生成することがポイントです。例えば、QRegExp の場合、パターンシンタックスが何になるかがわかる前に、 setPattern() で指定された “'''.'''”のパターンをコンパイルしてしまうのは時期尚早です。
timer.start();
<br />プロパティは連続して指定されることがあります。この場合は注意深く先に進める必要があります。現在のスタイルによって決まる &quot;デフォルトのアイコンサイズ&amp;quot; と QToolButton の &quot;iconSize&amp;quot; プロパティの例を見てみましょう。
</code>
<br /></code><br /> toolButton</s>&gt;iconSize(); // 現在のスタイルのデフォルトを返す<br /> toolButton-&gt;setStyle(otherStyle);<br /> toolButton-&gt;iconSize(); // otherStyle のデフォルトを返す<br /> toolButton-&gt;setIconSize(QSize(52, 52));<br /> toolButton-&gt;iconSize(); // (52, 52) を返す<br /> toolButton-&gt;setStyle(yetAnotherStyle);<br /> toolButton-&gt;iconSize(); // (52, 52) を返す<br /><code>
 
''プロパティ'' はそのオブジェクトの状態の一部となる概念的な属性を意味します。Q_PROPERTY かどうかはここでは関係ありません。使用可能な場合、プロパティの設定は順序に依存すべきではありません。つまり、個々のプロパティは直交であるべきです。例えば、上記の例は以下のようにも書くことができます。
 
<code>
QTimer timer;
timer.setSingleShot(true);
timer.setInterval(1000);
timer.start();
</code>
 
''簡単に'' 以下のように記述することも可能です。
 
<code>timer.start(1000)<code>
 
同じことを QRegExp でも見てみましょう。
 
</code>
QRegExp regExp;
regExp.setCaseSensitive(Qt::CaseInsensitive);
regExp.setPattern("'''.'''");
regExp.setPatternSyntax(Qt::WildcardSyntax);
<code>
 
このような API の実装では、内部のオブジェクトが遅延するように生成することがポイントです。例えば、QRegExp の場合、パターンシンタックスが何になるかがわかる前に、 setPattern() で指定された “'''.'''”のパターンをコンパイルしてしまうのは時期尚早です。
 
プロパティは連続して指定されることがあります。この場合は注意深く先に進める必要があります。現在のスタイルによって決まる "デフォルトのアイコンサイズ" と QToolButton の "iconSize" プロパティの例を見てみましょう。
 
</code>
toolButton->iconSize(); // 現在のスタイルのデフォルトを返す
toolButton->setStyle(otherStyle);
toolButton->iconSize(); // otherStyle のデフォルトを返す
toolButton->setIconSize(QSize(52, 52));
toolButton->iconSize(); // (52, 52) を返す
toolButton->setStyle(yetAnotherStyle);
toolButton->iconSize(); // (52, 52) を返す
<code>


一度 iconSize を設定すると、この設定が有効になり、スタイルの変更では設定が変わりません。これは '''良いことです''' 。プロパティをリセットできるようにしておくと便利な場合もあり、これには2つのアプローチがあります。
一度 iconSize を設定すると、この設定が有効になり、スタイルの変更では設定が変わりません。これは '''良いことです''' 。プロパティをリセットできるようにしておくと便利な場合もあり、これには2つのアプローチがあります。


* 特別な値 (QSize() や –1、Qt::Alignment(0) など) を &quot;リセット&amp;quot; の意味で渡す
* 特別な値 (QSize() や –1、Qt::Alignment(0) など) を "リセット" の意味で渡す


* resetFoo() や unsetFoo() などの関数を明示的に用意する
* resetFoo() や unsetFoo() などの関数を明示的に用意する


iconSize の場合は (QSize(–1, <s>1) を意味する) QSize() を &quot;リセット&amp;quot; の意味とすることで十分でしょう。
iconSize の場合は (QSize(–1, -1) を意味する) QSize() を "リセット" の意味とすることで十分でしょう。
<br />取得関数が設定されたものと異なるものを返す場合があります。例えば widget</s>&gt;setEnabled(true) を実行した場合でも widget-&gt;isEnabled() が false を返すことがあり得ます。これは親が無効な場合です。通常はこれがチェックしたい点 (親が無効な場合は子ウィジェットもグレーアウトされるべきで、そのウィジェット自身も無効として振る舞うべきであると同時に、この設定は内部では保持されており、実際は &quot;有効&amp;quot; であり、親が再度有効になるのを待っているということを) なので、問題はありません。ただし、この動作はドキュメントに正確に記載されるべきですが。
 
取得関数が設定されたものと異なるものを返す場合があります。例えば widget->setEnabled(true) を実行した場合でも widget->isEnabled() が false を返すことがあり得ます。これは親が無効な場合です。通常はこれがチェックしたい点 (親が無効な場合は子ウィジェットもグレーアウトされるべきで、そのウィジェット自身も無効として振る舞うべきであると同時に、この設定は内部では保持されており、実際は "有効" であり、親が再度有効になるのを待っているということを) なので、問題はありません。ただし、この動作はドキュメントに正確に記載されるべきですが。


== C++ 固有の問題 ==
== C++ 固有の問題 ==
Line 72: Line 110:
出力パラメーターには、ポインタと参照ではどちらがベストでしょうか?
出力パラメーターには、ポインタと参照ではどちらがベストでしょうか?


</code><br /> void getHsv(int *h, int *s, int *v) const<br /> void getHsv(int &amp;h, int &amp;s, int &amp;v) const<br /><code>
</code>
void getHsv(int *h, int *s, int *v) const
void getHsv(int &amp;h, int &amp;s, int &amp;v) const
<code>


ほとんどの C++ の本では可能な場合は参照が推奨されています。一般的な観点では、ポインタよりも参照の方が &quot;より安全でより適切&amp;quot; とされています。これに反して、Qt ではポインタを選択する傾向にあります。これはユーザーのコードがより読みやすくなるからです。それでは比較してみましょう。
ほとんどの C++ の本では可能な場合は参照が推奨されています。一般的な観点では、ポインタよりも参照の方が "より安全でより適切" とされています。これに反して、Qt ではポインタを選択する傾向にあります。これはユーザーのコードがより読みやすくなるからです。それでは比較してみましょう。


</code><br /> color.getHsv(&amp;h, &amp;s, &amp;v);<br /> color.getHsv(h, s, v);<br /><code>
</code>
color.getHsv(&amp;h, &amp;s, &amp;v);
color.getHsv(h, s, v);
<code>


h、s、v がこの関数呼び出しで変更される可能性が高いことが明らかなのは最初の行だけでしょう。
h、s、v がこの関数呼び出しで変更される可能性が高いことが明らかなのは最初の行だけでしょう。
Line 84: Line 128:
C++ ではメンバ関数をバーチャルで宣言する基本的な目的は、派生クラスでその関数をオーバーロードし、その振る舞いをカスタマイズできるようにすることです。その関数をバーチャルにする目的はその関数の既にある呼び出しで、代わりに自分のコードを実行するためです。ある関数を外部から呼び出すコードがどこにもない場合には、その関数をバーチャルとして宣言することに慎重になるべきです。
C++ ではメンバ関数をバーチャルで宣言する基本的な目的は、派生クラスでその関数をオーバーロードし、その振る舞いをカスタマイズできるようにすることです。その関数をバーチャルにする目的はその関数の既にある呼び出しで、代わりに自分のコードを実行するためです。ある関数を外部から呼び出すコードがどこにもない場合には、その関数をバーチャルとして宣言することに慎重になるべきです。


</code><br /> // Qt 3 の QTextEdit: バーチャルである理由がないメンバ関数の一覧<br /> virtual void resetFormat();<br /> virtual void setUndoDepth( int d );<br /> virtual void setFormat( QTextFormat '''f, int flags );<br /> virtual void ensureCursorVisible();<br /> virtual void placeCursor( const QPoint &amp;pos;, QTextCursorc = 0 );<br /> virtual void moveCursor( CursorAction action, bool select );<br /> virtual void doKeyboardAction( KeyboardAction action );<br /> virtual void removeSelectedText( int selNum = 0 );<br /> virtual void removeSelection( int selNum = 0 );<br /> virtual void setCurrentFont( const QFont &amp;f );<br /> virtual void setOverwriteMode( bool b ) { overWrite = b; }<br /><code>
</code>
<br />QTextEdit を Qt 3 から Qt 4 に移植した際に、ほとんどのバーチャル関数は削除されました。面白いことに(<s>期待はしていませんでしたが</s> 予期していなかったわけではないのですが)、大きな問題はありませんでした。なぜか?それは Qt 3 では QTextEdit のためにはポリモーフィズムが使われていなかったからです。Qt 3 の中ではこれらの関数は呼び出していませんでした。呼び出していたのはユーザーです。端的に言うと、QTextEdit の派生クラスを作成する理由は無く、ユーザーがこれらの関数を呼び出さない限り、これらの関数を再実装する理由も無かったのです。Qt の外のアプリケーションでポリモーフィズムが必要な場合は、自分でポリモーフィズムを追加することができたのです。
// Qt 3 の QTextEdit: バーチャルである理由がないメンバ関数の一覧
<br />h4. バーチャル関数を避ける
virtual void resetFormat();
<br />Qt では我々は様々な理由によりバーチャル関数の数を最小限にするように努めました。バーチャル関数の呼び出しは、制御不能なノードが呼び出しグラフの中に入るため(出力が予測不可能になり)、バグの修正を困難にします。以下のように、再実装したバーチャル関数の中でとんでもない処理をする人もいます。
virtual void setUndoDepth( int d );
<br />''' イベントの送信
virtual void setFormat( QTextFormat '''f, int flags );
virtual void ensureCursorVisible();
virtual void placeCursor( const QPoint &amp;pos;, QTextCursorc = 0 );
virtual void moveCursor( CursorAction action, bool select );
virtual void doKeyboardAction( KeyboardAction action );
virtual void removeSelectedText( int selNum = 0 );
virtual void removeSelection( int selNum = 0 );
virtual void setCurrentFont( const QFont &amp;f );
virtual void setOverwriteMode( bool b ) { overWrite = b; }
<code>
 
QTextEdit を Qt 3 から Qt 4 に移植した際に、ほとんどのバーチャル関数は削除されました。面白いことに(-期待はしていませんでしたが- 予期していなかったわけではないのですが)、大きな問題はありませんでした。なぜか?それは Qt 3 では QTextEdit のためにはポリモーフィズムが使われていなかったからです。Qt 3 の中ではこれらの関数は呼び出していませんでした。呼び出していたのはユーザーです。端的に言うと、QTextEdit の派生クラスを作成する理由は無く、ユーザーがこれらの関数を呼び出さない限り、これらの関数を再実装する理由も無かったのです。Qt の外のアプリケーションでポリモーフィズムが必要な場合は、自分でポリモーフィズムを追加することができたのです。
 
h4. バーチャル関数を避ける
 
Qt では我々は様々な理由によりバーチャル関数の数を最小限にするように努めました。バーチャル関数の呼び出しは、制御不能なノードが呼び出しグラフの中に入るため(出力が予測不可能になり)、バグの修正を困難にします。以下のように、再実装したバーチャル関数の中でとんでもない処理をする人もいます。
 
''' イベントの送信


* シグナルの発生
* シグナルの発生
Line 94: Line 155:
* (例えばモーダルなファイルダイアログを開くなどによる) イベントループの実行
* (例えばモーダルなファイルダイアログを開くなどによる) イベントループの実行


* (例えば &quot;delete this&amp;quot; となるような) オブジェクト自体の破棄
* (例えば "delete this" となるような) オブジェクト自体の破棄


これ以外にも、バーチャル関数の過度の使用を避ける理由は山ほどあります。
これ以外にも、バーチャル関数の過度の使用を避ける理由は山ほどあります。
Line 120: Line 181:
あるクラスのコピーや代入を可能にしたり、値による比較をしたい場合には、コピーコンストラクタと代入演算子、等価演算子が必要でしょう。
あるクラスのコピーや代入を可能にしたり、値による比較をしたい場合には、コピーコンストラクタと代入演算子、等価演算子が必要でしょう。


</code><br /> class CopyClass {<br /> public:<br /> CopyClass();<br /> CopyClass(const CopyClass &amp;other;);<br /> ~CopyClass();<br /> CopyClass &amp;operator;=(const CopyClass &amp;other;);<br /> bool operator==(const CopyClass &amp;other;) const;<br /> bool operator!=(const CopyClass &amp;other;) const;
</code>
class CopyClass {
public:
CopyClass();
CopyClass(const CopyClass &amp;other;);
~CopyClass();
CopyClass &amp;operator;=(const CopyClass &amp;other;);
bool operator==(const CopyClass &amp;other;) const;
bool operator!=(const CopyClass &amp;other;) const;


virtual void setValue(int v);<br /> };<br /><code>
virtual void setValue(int v);
};
<code>


このクラスの派生クラスを作成すると、思いもよらないことがあなたのコードで起こります。一般的に、バーチャル関数とバーチャルなデストラクタを持たない場合はユーザーは派生クラスを作成しポリモーフィズムを使用することはできません。しかし、バーチャル関数やバーチャルなデストラクタを追加した場合、それがそのまま派生クラスを作成する理由となり、状況は複雑になります。 ''バーチャル修飾子で宣言するのはとても簡単です。'' しかし、混乱と破滅(可読性の低いコード)に陥るでしょう。以下の例で考えてみましょう。
このクラスの派生クラスを作成すると、思いもよらないことがあなたのコードで起こります。一般的に、バーチャル関数とバーチャルなデストラクタを持たない場合はユーザーは派生クラスを作成しポリモーフィズムを使用することはできません。しかし、バーチャル関数やバーチャルなデストラクタを追加した場合、それがそのまま派生クラスを作成する理由となり、状況は複雑になります。 ''バーチャル修飾子で宣言するのはとても簡単です。'' しかし、混乱と破滅(可読性の低いコード)に陥るでしょう。以下の例で考えてみましょう。


</code><br /> class OtherClass {<br /> public:<br /> const CopyClass &amp;instance;() const; // これは何を返すのか?何を代入すべきか?<br /> };<br /><code>
</code>
class OtherClass {
public:
const CopyClass &amp;instance;() const; // これは何を返すのか?何を代入すべきか?
};
<code>


(このセクションは工事中です)
(このセクションは工事中です)
Line 132: Line 208:
=== const について ===
=== const について ===


C++ にはあるものが変わらない/副作用が無いことを表すための &quot;const&amp;quot; というキーワードがあります。これは単純な値、ポインタ、ポインタが示すものに対して有効で、さらにオブジェクトの状態を変えない関数の特別な属性として使われます。
C++ にはあるものが変わらない/副作用が無いことを表すための "const" というキーワードがあります。これは単純な値、ポインタ、ポインタが示すものに対して有効で、さらにオブジェクトの状態を変えない関数の特別な属性として使われます。


const がついたもの自体にはそれほど大きな価値がありません。&amp;quot;const&amp;quot; キーワードさえ持っていないプログラミング言語もたくさんあります。しかしこのことがそのまま不要であるという理由にはなりません。実際、C++ のソースコードから、関数の const のオーバーロードを消し、&amp;quot;const&amp;quot; キーワードを全て検索して削除してみてもほとんどのものは、コンパイル、動作ともに問題ないでしょう。しかし、実用性を考えると、&amp;quot;const&amp;quot; の使用はとても重要です。
const がついたもの自体にはそれほど大きな価値がありません。"const" キーワードさえ持っていないプログラミング言語もたくさんあります。しかしこのことがそのまま不要であるという理由にはなりません。実際、C++ のソースコードから、関数の const のオーバーロードを消し、"const" キーワードを全て検索して削除してみてもほとんどのものは、コンパイル、動作ともに問題ないでしょう。しかし、実用性を考えると、"const" の使用はとても重要です。


Qt の API 設計の中で、&amp;quot;const&amp;quot; が適切に使用されているものをいくつか見てみましょう。
Qt の API 設計の中で、"const" が適切に使用されているものをいくつか見てみましょう。


==== 引き数の const ポインタ ====
==== 引き数の const ポインタ ====
Line 146: Line 222:
変更前:
変更前:


</code><br /> bool QWidget::isVisibleTo(QWidget *ancestor) const;<br /> bool QWidget::isEnabledTo(QWidget *ancestor) const;<br /> QPoint QWidget::mapFrom(QWidget *ancestor, const QPoint &amp;pos;) const;<br /><code>
</code>
bool QWidget::isVisibleTo(QWidget *ancestor) const;
bool QWidget::isEnabledTo(QWidget *ancestor) const;
QPoint QWidget::mapFrom(QWidget *ancestor, const QPoint &amp;pos;) const;
<code>


QWidget には const ではないポインタを引き数に取る const 関数がたくさんあります。これらの関数は引き数で渡された widget を変更することは可能ですが、自分自身は変更できません。このような関数は const_cast と一緒になっています。これらの関数は const ポインタを引き数に取る方がいいでしょう。
QWidget には const ではないポインタを引き数に取る const 関数がたくさんあります。これらの関数は引き数で渡された widget を変更することは可能ですが、自分自身は変更できません。このような関数は const_cast と一緒になっています。これらの関数は const ポインタを引き数に取る方がいいでしょう。
Line 152: Line 232:
変更後:
変更後:


</code><br /> bool QWidget::isVisibleTo(const QWidget *ancestor) const;<br /> bool QWidget::isEnabledTo(const QWidget *ancestor) const;<br /> QPoint QWidget::mapFrom(const QWidget *ancestor, const QPoint &amp;pos;) const;<br /><code>
</code>
bool QWidget::isVisibleTo(const QWidget *ancestor) const;
bool QWidget::isEnabledTo(const QWidget *ancestor) const;
QPoint QWidget::mapFrom(const QWidget *ancestor, const QPoint &amp;pos;) const;
<code>


QGraphicsItem ではこのような変更をしました。しかし、QWidget の変更は Qt 5 まで待つ必要があります。
QGraphicsItem ではこのような変更をしました。しかし、QWidget の変更は Qt 5 まで待つ必要があります。


</code><br /> bool isVisibleTo(const QGraphicsItem *parent) const;<br /> QPointF mapFromItem (const QGraphicsItem *item, const QPointF &amp;point;) const;<br /><code>
</code>
bool isVisibleTo(const QGraphicsItem *parent) const;
QPointF mapFromItem (const QGraphicsItem *item, const QPointF &amp;point;) const;
<code>


==== 戻り値の const ====
==== 戻り値の const ====
Line 162: Line 249:
関数呼び出しの戻り値で、参照を返さないものは R-value と呼ばれます。
関数呼び出しの戻り値で、参照を返さないものは R-value と呼ばれます。


クラスではない R-value は常に cv 非修飾型です。文法的には &quot;const&amp;quot; をつけることが可能であっても、アクセス権に関するものは何も変更しないため、あまり意味はありません。
クラスではない R-value は常に cv 非修飾型です。文法的には "const" をつけることが可能であっても、アクセス権に関するものは何も変更しないため、あまり意味はありません。


最近のほとんどのコンパイラではこのようなコードのコンパイル時に警告が表示されるでしょう。
最近のほとんどのコンパイラではこのようなコードのコンパイル時に警告が表示されるでしょう。
Line 170: Line 257:
“const” を追加しなければそういったアクセスもできますが、それが必要となるケースはごく稀です。なぜならば、加えた変更も R-value オブジェクトの有効期間の終了と共に消えてしまうからです。
“const” を追加しなければそういったアクセスもできますが、それが必要となるケースはごく稀です。なぜならば、加えた変更も R-value オブジェクトの有効期間の終了と共に消えてしまうからです。


サンプル:<br /></code><br /> struct Foo<br /> {<br /> void setValue(int v) { value = v; }<br /> int value;<br /> };
サンプル:
</code>
struct Foo
{
void setValue(int v) { value = v; }
int value;
};


Foo foo()<br /> {<br /> return Foo();<br /> }
Foo foo()
{
return Foo();
}


const Foo cfoo()<br /> {<br /> return Foo();<br /> }
const Foo cfoo()
{
return Foo();
}


int main()<br /> {<br /> // 以下の文はコンパイルは通ります。foo() は const ではない R-value のため<br /> // (一般的に L-value を必要とする)代入はできませんが、<br /> // メンバへのアクセスは L-value となります。<br /> foo().value = 1; // OK だが、一時オブジェクトは文の最後で破棄されます。
int main()
{
// 以下の文はコンパイルは通ります。foo() は const ではない R-value のため
// (一般的に L-value を必要とする)代入はできませんが、
// メンバへのアクセスは L-value となります。
foo().value = 1; // OK だが、一時オブジェクトは文の最後で破棄されます。


// 以下の文はコンパイルは通ります。foo() は const ではない R-value のため<br /> // 代入はできないが、(const でなくても) メンバ関数を呼ぶことはできます。<br /> foo().setValue(1); // OK だが、一時オブジェクトは文の最後で破棄されます。
// 以下の文はコンパイルは通ります。foo() は const ではない R-value のため
// 代入はできないが、(const でなくても) メンバ関数を呼ぶことはできます。
foo().setValue(1); // OK だが、一時オブジェクトは文の最後で破棄されます。


// 以下の文はコンパイルが_通りません_。foo() は const なメンバを持つ<br /> // const な R-value のため、メンバアクセスでの代入はできません。<br /> cfoo().value = 1; // NG
// 以下の文はコンパイルが_通りません_。foo() は const なメンバを持つ
// const な R-value のため、メンバアクセスでの代入はできません。
cfoo().value = 1; // NG


// 以下の文はコンパイルが_通りません_。foo() は const なメンバを持つ<br /> // const な R-value のため、const ではないメンバ関数を呼び出せません。<br /> cfoo().setValue(1); // NG<br /> }<br /><code>
// 以下の文はコンパイルが_通りません_。foo() は const なメンバを持つ
// const な R-value のため、const ではないメンバ関数を呼び出せません。
cfoo().setValue(1); // NG
}
<code>


==== 戻り値: ポインタ vs const ポインタ ====
==== 戻り値: ポインタ vs const ポインタ ====
Line 190: Line 302:
以下のサンプルで const ではないポインタを返す const 関数を使用した、数ある const の抜け道の1つを見てみましょう。
以下のサンプルで const ではないポインタを返す const 関数を使用した、数ある const の抜け道の1つを見てみましょう。


</code><br /> QVariant CustomWidget::inputMethodQuery(Qt::InputMethodQuery query) const<br /> {<br /> moveBy(10, 10); // コンパイルできない!<br /> window()<s>&gt;childAt(mapTo(window(), rect().center()))</s>&gt;moveBy(10, 10); // コンパイルできる!<br /> }<br /><code>
</code>
QVariant CustomWidget::inputMethodQuery(Qt::InputMethodQuery query) const
{
moveBy(10, 10); // コンパイルできない!
window()->childAt(mapTo(window(), rect().center()))->moveBy(10, 10); // コンパイルできる!
}
<code>


const ポインタを返す関数では(期待どおりかどうかは別として) this に対する副作用を少なくともある程度の範囲において防いでいます。しかし、const ポインタやそのリストを返したいと思うのはどのような関数でしょうか?const の正しいアプローチを取った場合、メンバの1つへのポインタ(もしくはその配列)を返す全ての const 関数は、const ポインタを返さなければいけません。しかし残念なことに現実的には API が使いづらくなります。
const ポインタを返す関数では(期待どおりかどうかは別として) this に対する副作用を少なくともある程度の範囲において防いでいます。しかし、const ポインタやそのリストを返したいと思うのはどのような関数でしょうか?const の正しいアプローチを取った場合、メンバの1つへのポインタ(もしくはその配列)を返す全ての const 関数は、const ポインタを返さなければいけません。しかし残念なことに現実的には API が使いづらくなります。


</code><br /> QGraphicsScene scene;<br /> // … シーンの構築
</code>
QGraphicsScene scene;
// … シーンの構築


foreach (const QGraphicsItem '''item, scene.items()) {<br /> item-&gt;setPos(qrand() % 500, qrand() % 500); // item が const ポインタなのでコンパイルできない!<br /> }<br /><code>
foreach (const QGraphicsItem '''item, scene.items()) {
<br />QGraphicsScene::items() は const 関数のため、const ポインタの配列を返すべきだということになるかもしれません。
item->setPos(qrand() % 500, qrand() % 500); // item が const ポインタなのでコンパイルできない!
<br />Qt では、我々はほとんど全てで const ではないパターンを使用しています。つまり、実用的なアプローチを選択しました。const ではないポインタを返し、その不正使用により起こりうる問題よりも、const ポインタを返すことによって起こりうる const_cast の過度の利用の方が深刻だと考えたのです。
}
<br />h4. 戻り値: 値か const 参照か
<code>
<br />戻り値のためのオブジェクトのコピーを保持している場合、const 参照を返すのが最も速いアプローチです。しかし、これは我々が後でそのクラスのリファクタリングをする際には足かせとなります。 (d-ポインタを使用する方法により、Qt のクラスのメモリ表現をいつでも変えることができます。しかし、バイナリ互換を保ったまま関数のシグネチャを &quot;const QFoo &amp;&quot; から &quot;QFoo&amp;quot; へ変更することはできません。) このため、処理速度が本当に求められる場合でリファクタリングの問題が無い場合(例えば QList::at())を除いて、一般的には &quot;const QFoo &amp;&quot; ではなく &quot;QFoo&amp;quot; を返すようにしています。
 
<br />h4. const vs オブジェクトの状態
QGraphicsScene::items() は const 関数のため、const ポインタの配列を返すべきだということになるかもしれません。
<br />const の正当性は C++ に置ける vi/emacs 議論であり、このトピックはいくつかの分野で破綻しています(例えばポインタベースの関数)。
 
<br />しかし、const 関数はクラスの目に見える状態を変えないというのが一般的なルールです。状態とは &quot;自分と自分の責任の範囲&amp;quot; を意味します。これは const ではない関数がそのプライベートなメンバデータを変えることを意味するものではありません。また、const 関数でそれをできないわけでもありません。しかし、その関数はアクティブで、目に見える副作用を持ちます。const 関数は通常は目に見える副作用は行いません。
Qt では、我々はほとんど全てで const ではないパターンを使用しています。つまり、実用的なアプローチを選択しました。const ではないポインタを返し、その不正使用により起こりうる問題よりも、const ポインタを返すことによって起こりうる const_cast の過度の利用の方が深刻だと考えたのです。
<br /></code><br /> QSize size = widget-&gt;sizeHint(); // const<br /> widget-&gt;move(10, 10); // const ではない<br /><code>
 
<br />デリゲートは何か別のものの上に描画をするためのものです。デリゲートの状態にはその責任が含まれるため、描画対象の状態を含みます。描画には副作用があり、描画を行う対象の見た目(とそれに伴い、状態)を変更します。このため、paint() を const とするのは間違っています。全てのビューの paint() や QIcon の paint() も同様です。ある関数の const 性を明らかに破る目的ではない限り、const 関数の中で QIcon::paint() を呼ぶ人はいないでしょう。また、その場合は const_cast による明示的なキャストの方がいいでしょう。
h4. 戻り値: 値か const 参照か
<br /></code><br /> // QAbstractItemDelegate::paint は const<br /> void QAbstractItemDelegate::paint(QPainterpainter, const QStyleOptionViewItem &amp;option;, const QModelIndex &amp;index;) const
 
<br /> // QGraphicsItem::paint は const ではない<br /> void QGraphicsItem::paint(QPainter''' painter, const QStyleOptionGraphicsItem '''option, QWidget '''widget = 0)<br /><code>
戻り値のためのオブジェクトのコピーを保持している場合、const 参照を返すのが最も速いアプローチです。しかし、これは我々が後でそのクラスのリファクタリングをする際には足かせとなります。 (d-ポインタを使用する方法により、Qt のクラスのメモリ表現をいつでも変えることができます。しかし、バイナリ互換を保ったまま関数のシグネチャを "const QFoo &amp;" から "QFoo" へ変更することはできません。) このため、処理速度が本当に求められる場合でリファクタリングの問題が無い場合(例えば QList::at())を除いて、一般的には "const QFoo &amp;" ではなく "QFoo" を返すようにしています。
<br />h2. API のセマンティックとドキュメント
 
<br /><s>1 を関数に渡した場合にどうすべきかなど…。
h4. const vs オブジェクトの状態
<br />警告、致命的なエラーなど
 
<br />API には品質の保証が必要です。1番最初のものは決して正しくありません。API のテストをしなければなりません。この API を使用しているコードを見ることでユースケースを作成し、そのコードが可読かどうかを確認してください。
const の正当性は C++ に置ける vi/emacs 議論であり、このトピックはいくつかの分野で破綻しています(例えばポインタベースの関数)。
<br />他には、他の人にその API をドキュメントのあり/なしで使ってもらい、そのクラスのドキュメント(クラスの概要と個々の関数)を書く方法があります。
 
<br />const キーワードはあなたのためには &quot;なにもしてくれません&amp;quot; 。1つの関数に const/const ではないバージョンのオーバーロードを持つよりは、削除することを検討して下さい。
しかし、const 関数はクラスの目に見える状態を変えないというのが一般的なルールです。状態とは "自分と自分の責任の範囲" を意味します。これは const ではない関数がそのプライベートなメンバデータを変えることを意味するものではありません。また、const 関数でそれをできないわけでもありません。しかし、その関数はアクティブで、目に見える副作用を持ちます。const 関数は通常は目に見える副作用は行いません。
<br />h2. 命名の美学
 
<br />命名はおそらく API の設計における最重要課題です。そのクラスはなんと呼ばれるべきですか?メンバ関数はなんと呼ばれるべきですか?
</code>
<br />h3. 一般的な命名規則
QSize size = widget->sizeHint(); // const
<br />どんな種類の名前にも上手く適用できる規則がいくつかあります。まずはじめに、前述の通り、省略はしてはいけません。これは &quot;previous&amp;quot; &quot;prev&amp;quot; とするような明らかな場合でも、ユーザーがどの場合は省略形なのかを覚えなければならないため、長期的にはこれは良くありません。
widget->move(10, 10); // const ではない
<br />API 自体に矛盾があるのは当然よくありません。例えば、Qt 3 には activatePreviousWindow() と fetchPrev() がありました。&amp;quot;省略は無し&amp;quot; というルールにより矛盾のない API の実現が単純になります。
<code>
<br />もう1つのクラスを設計する際の重要で、それでいて繊細なルールは派生クラスのために名前空間を綺麗にするべきだということです。Qt 3 はこの原理に従っていないところもありました。分かりやすく説明するため、QToolButton を例にとります。Qt 3 の QToolButton で name()、caption()、text()、textLabel() を呼んだ場合、何を期待しますか? Qt Designer 上で QToolButton で色々試してみてください。
 
<br />* name プロパティは QObject から継承したもので、デバッグやテストの際に使用される内部のオブジェクト名を表します。
デリゲートは何か別のものの上に描画をするためのものです。デリゲートの状態にはその責任が含まれるため、描画対象の状態を含みます。描画には副作用があり、描画を行う対象の見た目(とそれに伴い、状態)を変更します。このため、paint() を const とするのは間違っています。全てのビューの paint() や QIcon の paint() も同様です。ある関数の const 性を明らかに破る目的ではない限り、const 関数の中で QIcon::paint() を呼ぶ人はいないでしょう。また、その場合は const_cast による明示的なキャストの方がいいでしょう。
<br />* caption プロパティは QWidget から継承したもので、ウィンドウのタイトルを表しますが、QToolButton は基本的には子ウィジェットとして使われるため実質的には意味がありません。
 
<br />* text プロパティは QButton から継承したもので、useTextLabel が true でない場合にボタン上に表示されます。
</code>
<br />* textLabel プロパティは QToolButton で定義され、useTextLabel が true の場合に表示されます。
// QAbstractItemDelegate::paint は const
<br />可読性の観点から、name は Qt 4 では objectName となりました。caption は windowTitle となり、QToolButton で text とは別にあった textLabel プロパティは無くなっています。
void QAbstractItemDelegate::paint(QPainterpainter, const QStyleOptionViewItem &amp;option;, const QModelIndex &amp;index;) const
<br />良い名前が思い浮かばない場合には、ドキュメント化はとてもいい方法になりえます。そのアイテム(クラス、関数、enum の値など)のドキュメントを作成してみて、最初にひらめいたの文章から決めるのです。正確な名前が見当たらない場合、そのアイテムがあるべきではないというサインの可能性があります。もし全ての方法に失敗し、それでもそのコンセプトに意味がある場合、新しい名前を発明しましょう。&amp;quot;widget&amp;quot; &quot;event&amp;quot;&amp;quot;focus&amp;quot;&amp;quot;buddy&amp;quot; などはこの結果生まれたものです。
 
<br />h3. クラスの命名
// QGraphicsItem::paint は const ではない
<br />個々のクラスに完璧な名前をつけるのではなく、クラスのグループが分かるようにしましょう。例えば Qt 4 に含まれる、モデルを利用するビュークラスは全て View で終わる名前(QListView、QTableView、QTreeView)になっていて、対応するアイテムベースのクラスは Widget で終わる名前(QListWidget、QTableWidget、QTreeWidget)になっています。
void QGraphicsItem::paint(QPainter''' painter, const QStyleOptionGraphicsItem '''option, QWidget '''widget = 0)
<br />h3. Enum 型と値の命名
<code>
<br />enum を宣言する際、C++ では(Java や C# とは異なり)、型には関係なく enum 値が使われることを心に留めておかなければなりません。以下の例で一般的すぎる名前を enum 値につけた場合の危険性を見てみましょう。
 
<br /></code><br /> namespace Qt<br /> {<br /> enum Corner { TopLeft, BottomRight, … };<br /> enum CaseSensitivity { Insensitive, Sensitive };<br /> <br /> };
h2. API のセマンティックとドキュメント
<br /> tabWidget</s>&gt;setCornerWidget(widget, Qt::TopLeft);<br /> str.indexOf(&quot;$(QTDIR)&quot;, Qt::Insensitive);<br /><code>
 
<br />最後の行で、Insensitive は何を意味するのでしょう?我々の enum 型の命名のガイドラインではそれぞれの enum 値で、 enum の型名の少なくとも1つの部分を繰り返すことになっています。
-1 を関数に渡した場合にどうすべきかなど…。
<br /></code><br /> namespace Qt<br /> {<br /> enum Corner { TopLeftCorner, BottomRightCorner, … };<br /> enum CaseSensitivity { CaseInsensitive,<br /> CaseSensitive };<br /> <br /> };
 
<br /> tabWidget-&gt;setCornerWidget(widget, Qt::TopLeftCorner);<br /> str.indexOf(&quot;$(QTDIR)&quot;, Qt::CaseInsensitive);<br /><code>
警告、致命的なエラーなど
<br />enum の値を OR でまとめてフラグとして使用できる場合、その結果は int に格納するのが伝統的な方法ですが、これは型安全ではありません。Qt 4 では T が enum 型である QFlags&amp;amp;lt;T&amp;amp;gt; というテンプレートクラスを導入しました。Qt では利便性を考え、このようなフラグ型の名前を typedef してあるため、QFlags&amp;amp;lt;Qt::AlignmentFlag&amp;amp;gt; の代わりに Qt::Alignment と書くことができます。
 
<br />慣例的に、enum 型の名前には(一度に1つのフラグしか持たないため)単数形を使用していて、&amp;quot;flags&amp;quot; 型は複数形の名前にしてあります。例:
API には品質の保証が必要です。1番最初のものは決して正しくありません。API のテストをしなければなりません。この API を使用しているコードを見ることでユースケースを作成し、そのコードが可読かどうかを確認してください。
<br /></code><br /> enum RectangleEdge { LeftEdge, RightEdge, … };<br /> typedef QFlags&amp;amp;lt;RectangleEdge&amp;amp;gt; RectangleEdges;<br /><code>
 
<br />&quot;flags&amp;quot; 型の名前が単数形の場合もあります。この場合、enum 型は Flag で終わる名前にしています。
他には、他の人にその API をドキュメントのあり/なしで使ってもらい、そのクラスのドキュメント(クラスの概要と個々の関数)を書く方法があります。
<br /></code><br /> enum AlignmentFlag { AlignLeft, AlignTop, … };<br /> typedef QFlags&amp;amp;lt;AlignmentFlag&amp;amp;gt; Alignment;<br /><code>
 
<br />h3. 関数と引数の命名方法
const キーワードはあなたのためには "なにもしてくれません" 。1つの関数に const/const ではないバージョンのオーバーロードを持つよりは、削除することを検討して下さい。
<br />関数の命名の第一のルールは名前から副作用の有無が分かるようにすることです。Qt 3 では const 関数 QString::simplifyWhiteSpace() がこの規則を違反していました。この関数は名前の通り呼び出された文字列自体を変更するのではなく、QString を返していたからです。この関数は Qt 4 では QString::simplified() という名前に変更されています。
 
<br />引数の名前はその API を使用するコードには現れませんが、プログラマーにとっては重要な意味を持ちます。最近の IDE ではプログラマーがコードを書いている際に表示されることが多いため、引き数に適切な名前をつけ、ヘッダファイルと同じ名前をドキュメントでも使用することはとても大事なことです。
h2. 命名の美学
<br />h3. bool 型の取得関数、設定関数、プロパティの命名方法
 
<br />bool 型のプロパティに対する取得関数と設定関数の良い名前を探すことは常に難しい問題です。取得関数は checked() とすべきでしょうか?それとも isChecked() とすべきでしょうか。scrollBarsEnabled() と areScrollBarEnabled() ではどうでしょうか。
命名はおそらく API の設計における最重要課題です。そのクラスはなんと呼ばれるべきですか?メンバ関数はなんと呼ばれるべきですか?
<br />Qt 4 では取得関数の命名に以下のガイドラインを使用しました。
 
<br />''' 形容詞は接頭語 is をつける<br />''' isChecked()<br />'''''' isDown()<br />'''''' isEmpty()<br />'''''' isMovingEnabled()
h3. 一般的な命名規則
 
どんな種類の名前にも上手く適用できる規則がいくつかあります。まずはじめに、前述の通り、省略はしてはいけません。これは "previous" "prev" とするような明らかな場合でも、ユーザーがどの場合は省略形なのかを覚えなければならないため、長期的にはこれは良くありません。
 
API 自体に矛盾があるのは当然よくありません。例えば、Qt 3 には activatePreviousWindow() と fetchPrev() がありました。"省略は無し" というルールにより矛盾のない API の実現が単純になります。
 
もう1つのクラスを設計する際の重要で、それでいて繊細なルールは派生クラスのために名前空間を綺麗にするべきだということです。Qt 3 はこの原理に従っていないところもありました。分かりやすく説明するため、QToolButton を例にとります。Qt 3 の QToolButton で name()、caption()、text()、textLabel() を呼んだ場合、何を期待しますか? Qt Designer 上で QToolButton で色々試してみてください。
 
* name プロパティは QObject から継承したもので、デバッグやテストの際に使用される内部のオブジェクト名を表します。
 
* caption プロパティは QWidget から継承したもので、ウィンドウのタイトルを表しますが、QToolButton は基本的には子ウィジェットとして使われるため実質的には意味がありません。
 
* text プロパティは QButton から継承したもので、useTextLabel が true でない場合にボタン上に表示されます。
 
* textLabel プロパティは QToolButton で定義され、useTextLabel が true の場合に表示されます。
 
可読性の観点から、name は Qt 4 では objectName となりました。caption は windowTitle となり、QToolButton で text とは別にあった textLabel プロパティは無くなっています。
 
良い名前が思い浮かばない場合には、ドキュメント化はとてもいい方法になりえます。そのアイテム(クラス、関数、enum の値など)のドキュメントを作成してみて、最初にひらめいたの文章から決めるのです。正確な名前が見当たらない場合、そのアイテムがあるべきではないというサインの可能性があります。もし全ての方法に失敗し、それでもそのコンセプトに意味がある場合、新しい名前を発明しましょう。"widget" "event""focus""buddy" などはこの結果生まれたものです。
 
h3. クラスの命名
 
個々のクラスに完璧な名前をつけるのではなく、クラスのグループが分かるようにしましょう。例えば Qt 4 に含まれる、モデルを利用するビュークラスは全て View で終わる名前(QListView、QTableView、QTreeView)になっていて、対応するアイテムベースのクラスは Widget で終わる名前(QListWidget、QTableWidget、QTreeWidget)になっています。
 
h3. Enum 型と値の命名
 
enum を宣言する際、C++ では(Java や C# とは異なり)、型には関係なく enum 値が使われることを心に留めておかなければなりません。以下の例で一般的すぎる名前を enum 値につけた場合の危険性を見てみましょう。
 
</code>
namespace Qt
{
enum Corner { TopLeft, BottomRight, … };
enum CaseSensitivity { Insensitive, Sensitive };
};
 
tabWidget->setCornerWidget(widget, Qt::TopLeft);
str.indexOf("$(QTDIR)", Qt::Insensitive);
<code>
 
最後の行で、Insensitive は何を意味するのでしょう?我々の enum 型の命名のガイドラインではそれぞれの enum 値で、 enum の型名の少なくとも1つの部分を繰り返すことになっています。
 
</code>
namespace Qt
{
enum Corner { TopLeftCorner, BottomRightCorner, … };
enum CaseSensitivity { CaseInsensitive,
CaseSensitive };
};
 
tabWidget->setCornerWidget(widget, Qt::TopLeftCorner);
str.indexOf("$(QTDIR)", Qt::CaseInsensitive);
<code>
 
enum の値を OR でまとめてフラグとして使用できる場合、その結果は int に格納するのが伝統的な方法ですが、これは型安全ではありません。Qt 4 では T が enum 型である QFlags&amp;amp;lt;T&amp;amp;gt; というテンプレートクラスを導入しました。Qt では利便性を考え、このようなフラグ型の名前を typedef してあるため、QFlags&amp;amp;lt;Qt::AlignmentFlag&amp;amp;gt; の代わりに Qt::Alignment と書くことができます。
 
慣例的に、enum 型の名前には(一度に1つのフラグしか持たないため)単数形を使用していて、"flags" 型は複数形の名前にしてあります。例:
 
</code>
enum RectangleEdge { LeftEdge, RightEdge, … };
typedef QFlags&amp;amp;lt;RectangleEdge&amp;amp;gt; RectangleEdges;
<code>
 
"flags" 型の名前が単数形の場合もあります。この場合、enum 型は Flag で終わる名前にしています。
 
</code>
enum AlignmentFlag { AlignLeft, AlignTop, … };
typedef QFlags&amp;amp;lt;AlignmentFlag&amp;amp;gt; Alignment;
<code>
 
h3. 関数と引数の命名方法
 
関数の命名の第一のルールは名前から副作用の有無が分かるようにすることです。Qt 3 では const 関数 QString::simplifyWhiteSpace() がこの規則を違反していました。この関数は名前の通り呼び出された文字列自体を変更するのではなく、QString を返していたからです。この関数は Qt 4 では QString::simplified() という名前に変更されています。
 
引数の名前はその API を使用するコードには現れませんが、プログラマーにとっては重要な意味を持ちます。最近の IDE ではプログラマーがコードを書いている際に表示されることが多いため、引き数に適切な名前をつけ、ヘッダファイルと同じ名前をドキュメントでも使用することはとても大事なことです。
 
h3. bool 型の取得関数、設定関数、プロパティの命名方法
 
bool 型のプロパティに対する取得関数と設定関数の良い名前を探すことは常に難しい問題です。取得関数は checked() とすべきでしょうか?それとも isChecked() とすべきでしょうか。scrollBarsEnabled() と areScrollBarEnabled() ではどうでしょうか。
 
Qt 4 では取得関数の命名に以下のガイドラインを使用しました。
 
''' 形容詞は接頭語 is をつける
''' isChecked()
'''''' isDown()
'''''' isEmpty()
'''''' isMovingEnabled()


* 複数形の名詞に対する形容詞の場合は is をつけない
* 複数形の名詞に対する形容詞の場合は is をつけない
Line 273: Line 480:
1つの間違ったコンセプトは、何かをするために必要なコードが短ければ短いほど、良い API であるということです。コードが書かれるが1度であったとしても、そのコードは何度も何度も読まれます。
1つの間違ったコンセプトは、何かをするために必要なコードが短ければ短いほど、良い API であるということです。コードが書かれるが1度であったとしても、そのコードは何度も何度も読まれます。


</code><br /> QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical,<br /> 0, &quot;volume&amp;quot;);<br /><code>
</code>
QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical,
0, "volume");
<code>


これは以下に比べてとても読みにくい(だけではなく書きにくい)でしょう。
これは以下に比べてとても読みにくい(だけではなく書きにくい)でしょう。


</code><br /> QSlider '''slider = new QSlider(Qt::Vertical);<br /> slider-&gt;setRange(12, 18);<br /> slider-&gt;setPageStep(3);<br /> slider-&gt;setValue(13);<br /> slider-&gt;setObjectName(&quot;volume&amp;quot;);<br /><code>
</code>
<br />h3. bool 型の引数の罠
QSlider '''slider = new QSlider(Qt::Vertical);
<br />bool 型の引数は頻繁に理解しづらいコードにつながります。特に既存の関数に bool 型の引数を追加するのはほぼ例外なく間違いです。これにあてはまる Qt での例は repaint() で、この関数は背景を消去するかどうかの指定のための bool 型の引数をオプションでとります(デフォルトは true)。これにより、以下のようなコードが書かれます。
slider->setRange(12, 18);
<br /></code><br /> widget-&gt;repaint(false);<br /><code>
slider->setPageStep(3);
<br />初心者はこれを見て &quot;再描画はされない&amp;quot; と思うかもしれません。
slider->setValue(13);
<br />無駄な拡張を防ぐために、関数を1つ追加する代わりに bool 型の引数を追加したということは明らかですが、逆に無駄な拡張をもたらしています。Qt ユーザーの中のどのくらいの人が以下の3つの行の本当の意味での違いを知っているでしょうか。
slider->setObjectName("volume");
<br /></code><br /> widget-&gt;repaint();<br /> widget-&gt;repaint(true);<br /> widget-&gt;repaint(false);<br /><code>
<code>
<br />API を以下のようにすることでいくらか改善されるでしょう。
 
<br /></code><br /> widget-&gt;repaint();<br /> widget-&gt;repaintWithoutErasing();<br /><code>
h3. bool 型の引数の罠
<br />Qt 4 ではシンプルに、ウィジェットの背景を消去せずに描画するという選択自体を削除しました。Qt 4 ではダブルバッファリングをフレームワークとしてサポートしたため、この機能は無くなりました。
 
<br />さらにいくつかの例を示します。
bool 型の引数は頻繁に理解しづらいコードにつながります。特に既存の関数に bool 型の引数を追加するのはほぼ例外なく間違いです。これにあてはまる Qt での例は repaint() で、この関数は背景を消去するかどうかの指定のための bool 型の引数をオプションでとります(デフォルトは true)。これにより、以下のようなコードが書かれます。
<br /></code><br /> widget-&gt;setSizePolicy(QSizePolicy::Fixed,<br /> QSizePolicy::Expanding, true);<br /> textEdit-&gt;insert(&quot;Where's Waldo?&quot;, true, true, false);<br /> QRegExp rx(&quot;moc_'''.c??&quot;, false, true);<br /><code>
 
</code>
widget->repaint(false);
<code>
 
初心者はこれを見て "再描画はされない" と思うかもしれません。
 
無駄な拡張を防ぐために、関数を1つ追加する代わりに bool 型の引数を追加したということは明らかですが、逆に無駄な拡張をもたらしています。Qt ユーザーの中のどのくらいの人が以下の3つの行の本当の意味での違いを知っているでしょうか。
 
</code>
widget->repaint();
widget->repaint(true);
widget->repaint(false);
<code>
 
API を以下のようにすることでいくらか改善されるでしょう。
 
</code>
widget->repaint();
widget->repaintWithoutErasing();
<code>
 
Qt 4 ではシンプルに、ウィジェットの背景を消去せずに描画するという選択自体を削除しました。Qt 4 ではダブルバッファリングをフレームワークとしてサポートしたため、この機能は無くなりました。
 
さらにいくつかの例を示します。
 
</code>
widget->setSizePolicy(QSizePolicy::Fixed,
QSizePolicy::Expanding, true);
textEdit->insert("Where's Waldo?", true, true, false);
QRegExp rx("moc_'''.c??", false, true);
<code>


1つの代替案は bool 型の引数を enum 型に置き換えることです。Qt 4 では QString の大文字小文字の区別に対してこれを適用しました。以下の2行を比較してみて下さい。
1つの代替案は bool 型の引数を enum 型に置き換えることです。Qt 4 では QString の大文字小文字の区別に対してこれを適用しました。以下の2行を比較してみて下さい。


</code><br /> str.replace(&quot;USER&quot;, user, false); // Qt 3<br /> str.replace(&quot;USER&quot;, user, Qt::CaseInsensitive); // Qt 4<br /><code>
</code>
str.replace("USER", user, false); // Qt 3
str.replace("USER", user, Qt::CaseInsensitive); // Qt 4
<code>


=== ものまねの罠 ===
=== ものまねの罠 ===
Line 302: Line 546:
このコンセプトを実際の例で見てみましょう。QProgressBar の API を Qt 3 と Qt 4 で比較してみます。Qt 3 では以下のようになっていました。
このコンセプトを実際の例で見てみましょう。QProgressBar の API を Qt 3 と Qt 4 で比較してみます。Qt 3 では以下のようになっていました。


</code><br /> class QProgressBar : public QWidget<br /> {<br /> <br /> public:<br /> int totalSteps() const;<br /> int progress() const;
</code>
class QProgressBar : public QWidget
{
public:
int totalSteps() const;
int progress() const;


const QString &amp;progressString;() const;<br /> bool percentageVisible() const;<br /> void setPercentageVisible(bool);
const QString &amp;progressString;() const;
bool percentageVisible() const;
void setPercentageVisible(bool);


void setCenterIndicator(bool on);<br /> bool centerIndicator() const;
void setCenterIndicator(bool on);
bool centerIndicator() const;


void setIndicatorFollowsStyle(bool);<br /> bool indicatorFollowsStyle() const;
void setIndicatorFollowsStyle(bool);
bool indicatorFollowsStyle() const;


public slots:<br /> void reset();<br /> virtual void setTotalSteps(int totalSteps);<br /> virtual void setProgress(int progress);<br /> void setProgress(int progress, int totalSteps);
public slots:
void reset();
virtual void setTotalSteps(int totalSteps);
virtual void setProgress(int progress);
void setProgress(int progress, int totalSteps);


protected:<br /> virtual bool setIndicator(QString &amp;progressStr;,<br /> int progress,<br /> int totalSteps);<br /> <br /> };<br /><code>
protected:
virtual bool setIndicator(QString &amp;progressStr;,
int progress,
int totalSteps);
};
<code>


この API はとても複雑で一貫性に欠けています。例えば、名前からは reset() と setTotalSteps()、setProgress() が緊密に連携していることは読み取れません。
この API はとても複雑で一貫性に欠けています。例えば、名前からは reset() と setTotalSteps()、setProgress() が緊密に連携していることは読み取れません。
Line 320: Line 584:
次のポイントは、progressString と percentage、indicator が同じものを指していることです。これは実際はプログレスバー上に表示されているテキストになります。通常はこのテキストはパーセントですが、setIndicator() 関数を使用することで様々なものが設定できます。新しい API は以下のようになります。
次のポイントは、progressString と percentage、indicator が同じものを指していることです。これは実際はプログレスバー上に表示されているテキストになります。通常はこのテキストはパーセントですが、setIndicator() 関数を使用することで様々なものが設定できます。新しい API は以下のようになります。


</code><br /> virtual QString text() const;<br /> void setTextVisible(bool visible);<br /> bool isTextVisible() const;<br /><code>
</code>
virtual QString text() const;
void setTextVisible(bool visible);
bool isTextVisible() const;
<code>


デフォルトでは、このテキストはパーセントの表示です。これは text() を再実装することで変更可能です。
デフォルトでは、このテキストはパーセントの表示です。これは text() を再実装することで変更可能です。
Line 326: Line 594:
Qt 3 の API にあった setCenterIndicator() と setIndicatorFollowsStyle() 関数はアライメントに関するものです。これらは setAlignment() という1つの関数に変更しました。
Qt 3 の API にあった setCenterIndicator() と setIndicatorFollowsStyle() 関数はアライメントに関するものです。これらは setAlignment() という1つの関数に変更しました。


</code><br /> void setAlignment(Qt::Alignment alignment);<br /><code>
</code>
void setAlignment(Qt::Alignment alignment);
<code>


プログラマーが setAlignment() を実行しない場合は、アライメントはスタイルによって決まります。Motif をベースにしたスタイルではテキストは中央に表示され、その他のスタイルでは右側に表示されます。
プログラマーが setAlignment() を実行しない場合は、アライメントはスタイルによって決まります。Motif をベースにしたスタイルではテキストは中央に表示され、その他のスタイルでは右側に表示されます。
Line 332: Line 602:
改善後の QProgressBar の API は以下の通りです。
改善後の QProgressBar の API は以下の通りです。


</code><br /> class QProgressBar : public QWidget<br /> {<br /> <br /> public:<br /> void setMinimum(int minimum);<br /> int minimum() const;<br /> void setMaximum(int maximum);<br /> int maximum() const;<br /> void setRange(int minimum, int maximum);<br /> int value() const;
</code>
class QProgressBar : public QWidget
{
public:
void setMinimum(int minimum);
int minimum() const;
void setMaximum(int maximum);
int maximum() const;
void setRange(int minimum, int maximum);
int value() const;


virtual QString text() const;<br /> void setTextVisible(bool visible);<br /> bool isTextVisible() const;<br /> Qt::Alignment alignment() const;<br /> void setAlignment(Qt::Alignment alignment);
virtual QString text() const;
void setTextVisible(bool visible);
bool isTextVisible() const;
Qt::Alignment alignment() const;
void setAlignment(Qt::Alignment alignment);


public slots:<br /> void reset();<br /> void setValue(int value);
public slots:
void reset();
void setValue(int value);


signals:<br /> void valueChanged(int value);<br /> <br /> };<br /><code>
signals:
void valueChanged(int value);
};
<code>


=== QAbstractPrintDialog と QAbstractPageSizeDialog ===
=== QAbstractPrintDialog と QAbstractPageSizeDialog ===


Qt 4.0 では QAbstractPrintDialog と QAbstractPageSizeDialog の2つのクラスが登場し、それぞれ QPrintDialog と QPageSizeDialog の基底クラスとなっています。これは、QAbstractPrint- や <s>PageSizeDialog のポインタを引数で取り処理を行う Qt の API がないため完全に無意味でした。qdoc をトリッキーに使用し、我々はこれらを隠しましたが、これらは不必要な抽象クラスの典型的な例でした。
Qt 4.0 では QAbstractPrintDialog と QAbstractPageSizeDialog の2つのクラスが登場し、それぞれ QPrintDialog と QPageSizeDialog の基底クラスとなっています。これは、QAbstractPrint- や -PageSizeDialog のポインタを引数で取り処理を行う Qt の API がないため完全に無意味でした。qdoc をトリッキーに使用し、我々はこれらを隠しましたが、これらは不必要な抽象クラスの典型的な例でした。
<br />これは ''良い'' 抽象化が間違っているということではありませんが、ファクトリなどの仕組みで QPrintDialog を変更する方法を用意すべきでした。#ifdef QTOPIA_PRINTDIALOG というマクロが宣言されているのがなによりの証拠です。
 
<br />h3. QAbstractItemModel
これは ''良い'' 抽象化が間違っているということではありませんが、ファクトリなどの仕組みで QPrintDialog を変更する方法を用意すべきでした。#ifdef QTOPIA_PRINTDIALOG というマクロが宣言されているのがなによりの証拠です。
<br />Qt 4 のモデル/ビューの問題の詳細については様々なところで述べられていますが、一般化した教訓としては、“QAbstractFoo” は、考えうる全ての派生クラスから、単に和集合をとって作ればよいわけではないということです。このような &quot;すべての可能性を抽象化&amp;quot; した基底クラスは決して良い解決法ではないということです。QAbstractItemModel はこの間違いを犯しました。このクラスは QTreeOfTablesModel であり、結果的に API が複雑になり、これが ''すべての素晴らしい派生クラスに継承されています'' 。
 
<br />単に抽象化をしても API が自動的に良くなるわけではありません。
h3. QAbstractItemModel
<br />h3. QLayoutIterator と QGLayoutIterator
 
<br />Qt 3 でカスタムレイアウトを作成するには QLayout と QGLayoutIterator(&quot;G&amp;quot; は generic からきています)の両方の派生クラスが必要でした。QGLayoutIterator の派生クラスのインスタンスのポインタは QLayoutIterator をラップしていたため、ユーザーは他のイテレータと同様に使用することができました。QLayoutIterator により以下のようなコードを書くことができました。
Qt 4 のモデル/ビューの問題の詳細については様々なところで述べられていますが、一般化した教訓としては、“QAbstractFoo” は、考えうる全ての派生クラスから、単に和集合をとって作ればよいわけではないということです。このような "すべての可能性を抽象化" した基底クラスは決して良い解決法ではないということです。QAbstractItemModel はこの間違いを犯しました。このクラスは QTreeOfTablesModel であり、結果的に API が複雑になり、これが ''すべての素晴らしい派生クラスに継承されています'' 。
<br /></code><br /> QLayoutIterator it = layout()</s>&gt;iterator();<br /> QLayoutItem **child;<br /> while ((child = it.current()) != 0) {<br /> if (child-&gt;widget() == myWidget) {<br /> it.takeCurrent();<br /> return;<br /> }<br /> ++it;<br /> }<br /><code>
 
単に抽象化をしても API が自動的に良くなるわけではありません。
 
h3. QLayoutIterator と QGLayoutIterator
 
Qt 3 でカスタムレイアウトを作成するには QLayout と QGLayoutIterator("G" は generic からきています)の両方の派生クラスが必要でした。QGLayoutIterator の派生クラスのインスタンスのポインタは QLayoutIterator をラップしていたため、ユーザーは他のイテレータと同様に使用することができました。QLayoutIterator により以下のようなコードを書くことができました。
 
</code>
QLayoutIterator it = layout()->iterator();
QLayoutItem **child;
while ((child = it.current()) != 0) {
if (child->widget() == myWidget) {
it.takeCurrent();
return;
}
++it;
}
<code>


Qt 4 では QGLayoutIterator クラス(と内部の Box と Grid の派生クラス)をなくし、QLayout の派生クラスで itemAt()、takeAt()、count() を実装するようにしました。
Qt 4 では QGLayoutIterator クラス(と内部の Box と Grid の派生クラス)をなくし、QLayout の派生クラスで itemAt()、takeAt()、count() を実装するようにしました。

Revision as of 09:07, 25 February 2015

[toc align_right="yes" depth="3"] English Русский 日本語

API 設計の原理原則

Qt が好評を得ている理由の1つが、首尾一貫した、簡単に学べる、非常に強力な API です。Qt の API を設計する上でこれまで蓄積してきたノウハウをこの記事で解説したいと思います。多くのガイドラインは普遍的なもので、中には慣例的なものもあります。我々がこのガイドラインに従う第一の理由は、既存の API の一貫性を保つためです。

基本的にこのガイドラインはパブリックな API に対するものですが、同様のテクニックをプライベートな API に対しても適用することを推奨しています。

Jasmin Blanchette による "Little Manual of API Design (PDF)":http://chaos.troll.no/~shausman/api-design/api-design.pdf も合わせて読むと良いでしょう。

良い API の6つの特徴

プログラマーにとって API とは、エンドユーザーにとっての GUI のようなものです。API が人間のプログラマーによって使われるという事実を強調する場合、API の ‘P’ は “Program” ではなく “Programmer” を意味します。

"Qt Quarterly 13 の API design に関する記事":http://doc.qt.nokia.com/qq/qq13-apis.html で、Matthias は API は最小限かつ完全で、明確でシンプルなセマンティックを持ち、直感的で、簡単に覚ることができ、可読なコードを導くべきだという彼の考えを示しました。

  • 最小限であれ: 最小限の API とは、クラス数を可能な限り少なくし、クラスのパブリックなメンバも可能な限り少なくするということです。これにより、理解が容易になり、覚えやすく、デバッグがしやすく、API の変更もしやすくなります。
  • 完全であれ: 完全な API とは必要な機能を備えているということです。最小限であることとは相反することがあります。メンバ関数が誤ったクラスにある場合には、その関数にたどり着く可能性が低くなります。
  • 明確でシンプルなセマンティックであれ: 一般的な設計と同様に、「驚き最小の原則」に従うべきです。一般的なものは簡単に対応できるようにします。一般的ではないものも対応できるようにすべきですが、それにはフォーカスしません。個々の問題への対処: 解決策を必要以上に一般化しないようにしましょう (例えば、Qt 3の QMimeSourceFactory は、QImageLoader と呼ばれる異なるAPI を持つものにすることもできました)。
  • 直感的であれ: コンピューター上の全てのものと同様に、API も直感的であるべきです。何が直感的であるかは経験やバックグランドによって異なります。それなりに経験のあるユーザーがドキュメントを読まずともはじめることができ、API を知らないプログラマーでも書かれたコードを理解することができる場合は API は直感的と言えるでしょう。
  • 覚えやすくあれ: API を簡単に覚えられるようにするには、首尾一貫した正確な名前をつけることです。理解できるパターンやコンセプトを採用し、省略はさけましょう。
  • 可読なコードを導け: コードは1度しか書かれませんが、何度も読まれ(デバッグされ、変更され)ます。読みやすいコードは書くのには時間がかかることもありますが、プロダクトのライフサイクルにおいては時間の節約になります。

最後に、様々なユーザーが様々な部分の API を使用することを心に留めてください。Qt のクラスのインスタンスを単に使用するのも直感的であるべきですが、ユーザーが派生クラス化をする前にそのドキュメントを読むことを想定してください。

静的なポリモーフィズム

同じようなクラスは同じような API を持つべきです。これは、実行時のポリモーフィズムが利用できる場所では、継承により実現することができます。しかし、ポリモーフィズムは設計時にも起こります。例えば QProgressBar の QSlider への変更や QString の QByteArray への変更など、API が似ていることでこれらの変更がとても簡単にできることがわかるでしょう。我々はこれを "静的なポリモーフィズム" と呼んでいます。

静的なポリモーフィズムにより API やプログラミングのパターンを覚えるのも簡単になります。関係するクラス群が同じような API を持つということは、それぞれのクラスが完璧な API をそれぞれ持つよりも結果として良い場合があります。

Qt ではやむを得ない場合を除き、実際の継承よりもこの静的なポリモーフィズムを採用することを好んでいます。これにより、Qt のパブリックなクラス数を少なく保つことができ、Qt の初心者でもドキュメントを見て使用方法を簡単に理解することができるようになります。

良い例: QDialogButtonBox と QMessageBox は "QAbstractButtonBox" のようなクラスを継承することなくボタンに関する同じような API (addButton(), setStandardButtons() など)を持ちます。

悪い例: QAbstractSocket は QTcpSocket と QUdpSocket により継承されていますが、この2つのクラスでは動作が大きく異なります。QAbstractSocket のポインタを有効に使用した(できた)例は見たことがありません。

難しい例: QBoxLayout は QHBoxLayout と QVBoxLayout の基底クラスです。利点: QBoxLayout を使用し、ツールバーで setOrientation() を呼ぶことで Horizontal/Vertical の設定ができる。欠点: 余計なクラスで、ユーザーは ((QBoxLayout *)hbox)->setOrientation(Qt::Vertical) とする事もできるが、あまり意味がない。

h2. プロパティに基づく API

新しい Qt のクラスは "プロパティに基づく API" を持つ傾向にあります。QTimer を例に見てみましょう。

 QTimer timer;
 timer.setInterval(1000);
 timer.setSingleShot(true);
 timer.start();

プロパティ はそのオブジェクトの状態の一部となる概念的な属性を意味します。Q_PROPERTY かどうかはここでは関係ありません。使用可能な場合、プロパティの設定は順序に依存すべきではありません。つまり、個々のプロパティは直交であるべきです。例えば、上記の例は以下のようにも書くことができます。

 QTimer timer;
 timer.setSingleShot(true);
 timer.setInterval(1000);
 timer.start();

簡単に 以下のように記述することも可能です。

timer.start(1000)<code>

同じことを QRegExp でも見てみましょう
QRegExp regExp;
regExp.setCaseSensitive(Qt::CaseInsensitive);
regExp.setPattern(".");
regExp.setPatternSyntax(Qt::WildcardSyntax);
このような API の実装では内部のオブジェクトが遅延するように生成することがポイントです例えばQRegExp の場合パターンシンタックスが何になるかがわかる前に setPattern() で指定された “'''.'''”のパターンをコンパイルしてしまうのは時期尚早です

プロパティは連続して指定されることがありますこの場合は注意深く先に進める必要があります現在のスタイルによって決まる "デフォルトのアイコンサイズ"  QToolButton  "iconSize" プロパティの例を見てみましょう
toolButton->iconSize(); // 現在のスタイルのデフォルトを返す
toolButton->setStyle(otherStyle);
toolButton->iconSize(); // otherStyle のデフォルトを返す
toolButton->setIconSize(QSize(52, 52));
toolButton->iconSize(); // (52, 52) を返す
toolButton->setStyle(yetAnotherStyle);
toolButton->iconSize(); // (52, 52) を返す
一度 iconSize を設定するとこの設定が有効になりスタイルの変更では設定が変わりませんこれは '''良いことです''' プロパティをリセットできるようにしておくと便利な場合もありこれには2つのアプローチがあります

* 特別な値 (QSize()  1Qt::Alignment(0) など)  "リセット" の意味で渡す

* resetFoo()  unsetFoo() などの関数を明示的に用意する

iconSize の場合は (QSize(1, -1) を意味する) QSize()  "リセット" の意味とすることで十分でしょう

取得関数が設定されたものと異なるものを返す場合があります例えば widget->setEnabled(true) を実行した場合でも widget->isEnabled()  false を返すことがあり得ますこれは親が無効な場合です通常はこれがチェックしたい点 (親が無効な場合は子ウィジェットもグレーアウトされるべきでそのウィジェット自身も無効として振る舞うべきであると同時にこの設定は内部では保持されており実際は "有効" であり親が再度有効になるのを待っているということを) なので問題はありませんただしこの動作はドキュメントに正確に記載されるべきですが

== C++ 固有の問題 ==

=== 値かオブジェクトか ===

=== ポインタか参照か ===

出力パラメーターにはポインタと参照ではどちらがベストでしょうか
void getHsv(int *h, int *s, int *v) const
void getHsv(int &h, int &s, int &v) const
ほとんどの C++ の本では可能な場合は参照が推奨されています一般的な観点ではポインタよりも参照の方が "より安全でより適切" とされていますこれに反してQt ではポインタを選択する傾向にありますこれはユーザーのコードがより読みやすくなるからですそれでは比較してみましょう
color.getHsv(&h, &s, &v);
color.getHsv(h, s, v);
hsv がこの関数呼び出しで変更される可能性が高いことが明らかなのは最初の行だけでしょう

=== バーチャル関数 ===

C++ ではメンバ関数をバーチャルで宣言する基本的な目的は派生クラスでその関数をオーバーロードしその振る舞いをカスタマイズできるようにすることですその関数をバーチャルにする目的はその関数の既にある呼び出しで代わりに自分のコードを実行するためですある関数を外部から呼び出すコードがどこにもない場合にはその関数をバーチャルとして宣言することに慎重になるべきです
// Qt 3 の QTextEdit: バーチャルである理由がないメンバ関数の一覧
virtual void resetFormat();
virtual void setUndoDepth( int d );
virtual void setFormat( QTextFormat f, int flags );
virtual void ensureCursorVisible();
virtual void placeCursor( const QPoint &pos;, QTextCursorc = 0 );
virtual void moveCursor( CursorAction action, bool select );
virtual void doKeyboardAction( KeyboardAction action );
virtual void removeSelectedText( int selNum = 0 );
virtual void removeSelection( int selNum = 0 );
virtual void setCurrentFont( const QFont &f );
virtual void setOverwriteMode( bool b ) { overWrite = b; }
QTextEdit  Qt 3 から Qt 4 に移植した際にほとんどのバーチャル関数は削除されました面白いことに(-期待はしていませんでしたが- 予期していなかったわけではないのですが)大きな問題はありませんでしたなぜかそれは Qt 3 では QTextEdit のためにはポリモーフィズムが使われていなかったからですQt 3 の中ではこれらの関数は呼び出していませんでした呼び出していたのはユーザーです端的に言うとQTextEdit の派生クラスを作成する理由は無くユーザーがこれらの関数を呼び出さない限りこれらの関数を再実装する理由も無かったのですQt の外のアプリケーションでポリモーフィズムが必要な場合は自分でポリモーフィズムを追加することができたのです

h4. バーチャル関数を避ける

Qt では我々は様々な理由によりバーチャル関数の数を最小限にするように努めましたバーチャル関数の呼び出しは制御不能なノードが呼び出しグラフの中に入るため(出力が予測不可能になり)バグの修正を困難にします以下のように再実装したバーチャル関数の中でとんでもない処理をする人もいます

''' イベントの送信

* シグナルの発生

* (例えばモーダルなファイルダイアログを開くなどによる) イベントループの実行

* (例えば "delete this" となるような) オブジェクト自体の破棄

これ以外にもバーチャル関数の過度の使用を避ける理由は山ほどあります

* バイナリコンパチビリティを保ちながらのバーチャル関数の追加移動削除ができない

* バーチャル関数は簡単にはオーバーロードできない

* ほとんど全てのバーチャル関数の呼び出しはコンパイラによる最適化やインライン化ができない

* 関数呼び出しに v-table のルックアップが必要になり通常の関数に比べ2倍遅くなる

* バーチャル関数を持つクラスでは値としてのコピーが難しくなる(可能だがとても厄介で非推奨)

バーチャル関数を持たないクラスほどバグが少なく一般的にメンテナンス性が高いことが経験的に分かっています

経験則では我々がツールキットとしてそして主なユーザーとして関数を呼び出すような場合でない限りその関数はおそらく非バーチャルであるべきです

==== バーチャル vs コピー可能 ====

ポリモーフィックなオブジェクトと値型のクラスは良いお友達ではありません

バーチャル関数を持つクラスのデストラクタはバーチャルでなければなりませんこれは派生クラスのデータが破棄されることなしに基底クラスが破棄されることによって起こるメモリリークを防ぐためです

あるクラスのコピーや代入を可能にしたり値による比較をしたい場合にはコピーコンストラクタと代入演算子等価演算子が必要でしょう
class CopyClass {
public:
CopyClass();
CopyClass(const CopyClass &other;);
~CopyClass();
CopyClass &operator;=(const CopyClass &other;);
bool operator==(const CopyClass &other;) const;
bool operator!=(const CopyClass &other;) const;

virtual void setValue(int v);

};
このクラスの派生クラスを作成すると思いもよらないことがあなたのコードで起こります一般的にバーチャル関数とバーチャルなデストラクタを持たない場合はユーザーは派生クラスを作成しポリモーフィズムを使用することはできませんしかしバーチャル関数やバーチャルなデストラクタを追加した場合それがそのまま派生クラスを作成する理由となり状況は複雑になります ''バーチャル修飾子で宣言するのはとても簡単です。'' しかし混乱と破滅(可読性の低いコード)に陥るでしょう以下の例で考えてみましょう
class OtherClass {
public:
const CopyClass &instance;() const; // これは何を返すのか?何を代入すべきか?
};
(このセクションは工事中です)

=== const について ===

C++ にはあるものが変わらない/副作用が無いことを表すための "const" というキーワードがありますこれは単純な値ポインタポインタが示すものに対して有効でさらにオブジェクトの状態を変えない関数の特別な属性として使われます

const がついたもの自体にはそれほど大きな価値がありません"const" キーワードさえ持っていないプログラミング言語もたくさんありますしかしこのことがそのまま不要であるという理由にはなりません実際C++ のソースコードから関数の const のオーバーロードを消し"const" キーワードを全て検索して削除してみてもほとんどのものはコンパイル動作ともに問題ないでしょうしかし実用性を考えると"const" の使用はとても重要です

Qt  API 設計の中で"const" が適切に使用されているものをいくつか見てみましょう

==== 引き数の const ポインタ ====

ポインタを引き数として取る const 関数はほぼ全て const ポインタを引き数に取ります

ある関数が実際に const と宣言されている場合副作用やそのオブジェクトの目に見える状態を変えることはありませんそれにも関わらず const ではない引き数が必要な場合はあるのでしょうかconst 関数は他の const 関数から呼ばれることが多くそれを考えるとconst ではないポインタが渡されることはほとんどありません(const_cast を使わない場合我々はできる限り const_cast の使用を控えています)

変更前:
bool QWidget::isVisibleTo(QWidget *ancestor) const;
bool QWidget::isEnabledTo(QWidget *ancestor) const;
QPoint QWidget::mapFrom(QWidget *ancestor, const QPoint &pos;) const;
QWidget には const ではないポインタを引き数に取る const 関数がたくさんありますこれらの関数は引き数で渡された widget を変更することは可能ですが自分自身は変更できませんこのような関数は const_cast と一緒になっていますこれらの関数は const ポインタを引き数に取る方がいいでしょう

変更後:
bool QWidget::isVisibleTo(const QWidget *ancestor) const;
bool QWidget::isEnabledTo(const QWidget *ancestor) const;
QPoint QWidget::mapFrom(const QWidget *ancestor, const QPoint &pos;) const;
QGraphicsItem ではこのような変更をしましたしかしQWidget の変更は Qt 5 まで待つ必要があります
bool isVisibleTo(const QGraphicsItem *parent) const;
QPointF mapFromItem (const QGraphicsItem *item, const QPointF &point;) const;
==== 戻り値の const ====

関数呼び出しの戻り値で参照を返さないものは R-value と呼ばれます

クラスではない R-value は常に cv 非修飾型です文法的には "const" をつけることが可能であってもアクセス権に関するものは何も変更しないためあまり意味はありません

最近のほとんどのコンパイラではこのようなコードのコンパイル時に警告が表示されるでしょう

クラス型の R-value  const を追加した場合const ではないメンバ関数へアクセスすることやそのメンバを直接操作することは禁止されています

const を追加しなければそういったアクセスもできますがそれが必要となるケースはごく稀ですなぜならば加えた変更も R-value オブジェクトの有効期間の終了と共に消えてしまうからです

サンプル:
struct Foo
{
void setValue(int v) { value = v; }
int value;
};

Foo foo()

{
return Foo();
}

const Foo cfoo()

{
return Foo();
}

int main()

{
// 以下の文はコンパイルは通ります。foo() は const ではない R-value のため
// (一般的に L-value を必要とする)代入はできませんが、
// メンバへのアクセスは L-value となります。
foo().value = 1; // OK だが、一時オブジェクトは文の最後で破棄されます。

// 以下の文はコンパイルは通ります。foo() は const ではない R-value のため

// 代入はできないが、(const でなくても) メンバ関数を呼ぶことはできます。
foo().setValue(1); // OK だが、一時オブジェクトは文の最後で破棄されます。

// 以下の文はコンパイルが_通りません_。foo() は const なメンバを持つ

// const な R-value のため、メンバアクセスでの代入はできません。
cfoo().value = 1; // NG

// 以下の文はコンパイルが_通りません_。foo() は const なメンバを持つ

// const な R-value のため、const ではないメンバ関数を呼び出せません。
cfoo().setValue(1); // NG
}
==== 戻り値: ポインタ vs const ポインタ ====

const 関数がポインタを返すかconst ポインタを返すかについてですがほとんどの人が C++ での const の正しさ のコンセプトが破綻していると感じるところです問題は自分の状態を変更しない const 関数が const ではないメンバのポインタを返すことで起こりますthis ポインタを返す単純な例でもオブジェクトの目に見える状態は変わりませんしその影響範囲の状態も変わりませんしかしプログラマーは間接的にオブジェクトのデータを変更することができます

以下のサンプルで const ではないポインタを返す const 関数を使用した数ある const の抜け道の1つを見てみましょう
QVariant CustomWidget::inputMethodQuery(Qt::InputMethodQuery query) const
{
moveBy(10, 10); // コンパイルできない!
window()->childAt(mapTo(window(), rect().center()))->moveBy(10, 10); // コンパイルできる!
}
const ポインタを返す関数では(期待どおりかどうかは別として) this に対する副作用を少なくともある程度の範囲において防いでいますしかしconst ポインタやそのリストを返したいと思うのはどのような関数でしょうかconst の正しいアプローチを取った場合メンバの1つへのポインタ(もしくはその配列)を返す全ての const 関数はconst ポインタを返さなければいけませんしかし残念なことに現実的には API が使いづらくなります
QGraphicsScene scene;
// … シーンの構築

foreach (const QGraphicsItem item, scene.items()) {

item->setPos(qrand() % 500, qrand() % 500); // item が const ポインタなのでコンパイルできない!
}
QGraphicsScene::items()  const 関数のためconst ポインタの配列を返すべきだということになるかもしれません

Qt では我々はほとんど全てで const ではないパターンを使用していますつまり実用的なアプローチを選択しましたconst ではないポインタを返しその不正使用により起こりうる問題よりもconst ポインタを返すことによって起こりうる const_cast の過度の利用の方が深刻だと考えたのです

h4. 戻り値: 値か const 参照か

戻り値のためのオブジェクトのコピーを保持している場合const 参照を返すのが最も速いアプローチですしかしこれは我々が後でそのクラスのリファクタリングをする際には足かせとなります (d-ポインタを使用する方法によりQt のクラスのメモリ表現をいつでも変えることができますしかしバイナリ互換を保ったまま関数のシグネチャを "const QFoo &amp;" から "QFoo" へ変更することはできません) このため処理速度が本当に求められる場合でリファクタリングの問題が無い場合(例えば QList::at())を除いて一般的には "const QFoo &amp;" ではなく "QFoo" を返すようにしています

h4. const vs オブジェクトの状態

const の正当性は C++ に置ける vi/emacs 議論でありこのトピックはいくつかの分野で破綻しています(例えばポインタベースの関数)

しかしconst 関数はクラスの目に見える状態を変えないというのが一般的なルールです状態とは "自分と自分の責任の範囲" を意味しますこれは const ではない関数がそのプライベートなメンバデータを変えることを意味するものではありませんまたconst 関数でそれをできないわけでもありませんしかしその関数はアクティブで目に見える副作用を持ちますconst 関数は通常は目に見える副作用は行いません
QSize size = widget->sizeHint(); // const
widget->move(10, 10); // const ではない
デリゲートは何か別のものの上に描画をするためのものですデリゲートの状態にはその責任が含まれるため描画対象の状態を含みます描画には副作用があり描画を行う対象の見た目(とそれに伴い状態)を変更しますこのためpaint()  const とするのは間違っています全てのビューの paint()  QIcon  paint() も同様ですある関数の const 性を明らかに破る目的ではない限りconst 関数の中で QIcon::paint() を呼ぶ人はいないでしょうまたその場合は const_cast による明示的なキャストの方がいいでしょう
// QAbstractItemDelegate::paint は const
void QAbstractItemDelegate::paint(QPainterpainter, const QStyleOptionViewItem &option;, const QModelIndex &index;) const
// QGraphicsItem::paint は const ではない
void QGraphicsItem::paint(QPainter painter, const QStyleOptionGraphicsItem option, QWidget widget = 0)
h2. API のセマンティックとドキュメント

-1 を関数に渡した場合にどうすべきかなど…。

警告致命的なエラーなど

API には品質の保証が必要です番最初のものは決して正しくありませんAPI のテストをしなければなりませんこの API を使用しているコードを見ることでユースケースを作成しそのコードが可読かどうかを確認してください

他には他の人にその API をドキュメントのありなしで使ってもらいそのクラスのドキュメント(クラスの概要と個々の関数)を書く方法があります

const キーワードはあなたのためには "なにもしてくれません" つの関数に const/const ではないバージョンのオーバーロードを持つよりは削除することを検討して下さい

h2. 命名の美学

命名はおそらく API の設計における最重要課題ですそのクラスはなんと呼ばれるべきですかメンバ関数はなんと呼ばれるべきですか

h3. 一般的な命名規則

どんな種類の名前にも上手く適用できる規則がいくつかありますまずはじめに前述の通り省略はしてはいけませんこれは "previous"  "prev" とするような明らかな場合でもユーザーがどの場合は省略形なのかを覚えなければならないため長期的にはこれは良くありません

API 自体に矛盾があるのは当然よくありません例えばQt 3 には activatePreviousWindow()  fetchPrev() がありました"省略は無し" というルールにより矛盾のない API の実現が単純になります

もう1つのクラスを設計する際の重要でそれでいて繊細なルールは派生クラスのために名前空間を綺麗にするべきだということですQt 3 はこの原理に従っていないところもありました分かりやすく説明するためQToolButton を例にとりますQt 3  QToolButton  name()caption()text()textLabel() を呼んだ場合何を期待しますか Qt Designer 上で QToolButton で色々試してみてください

* name プロパティは QObject から継承したものでデバッグやテストの際に使用される内部のオブジェクト名を表します

* caption プロパティは QWidget から継承したものでウィンドウのタイトルを表しますがQToolButton は基本的には子ウィジェットとして使われるため実質的には意味がありません

* text プロパティは QButton から継承したものでuseTextLabel  true でない場合にボタン上に表示されます

* textLabel プロパティは QToolButton で定義されuseTextLabel  true の場合に表示されます

可読性の観点からname  Qt 4 では objectName となりましたcaption  windowTitle となりQToolButton  text とは別にあった textLabel プロパティは無くなっています

良い名前が思い浮かばない場合にはドキュメント化はとてもいい方法になりえますそのアイテム(クラス関数enum の値など)のドキュメントを作成してみて最初にひらめいたの文章から決めるのです正確な名前が見当たらない場合そのアイテムがあるべきではないというサインの可能性がありますもし全ての方法に失敗しそれでもそのコンセプトに意味がある場合新しい名前を発明しましょう"widget"  "event""focus""buddy" などはこの結果生まれたものです

h3. クラスの命名

個々のクラスに完璧な名前をつけるのではなくクラスのグループが分かるようにしましょう例えば Qt 4 に含まれるモデルを利用するビュークラスは全て View で終わる名前(QListViewQTableViewQTreeView)になっていて対応するアイテムベースのクラスは Widget で終わる名前(QListWidgetQTableWidgetQTreeWidget)になっています

h3. Enum 型と値の命名

enum を宣言する際C++ では(Java  C# とは異なり)型には関係なく enum 値が使われることを心に留めておかなければなりません以下の例で一般的すぎる名前を enum 値につけた場合の危険性を見てみましょう
namespace Qt
{
enum Corner { TopLeft, BottomRight, … };
enum CaseSensitivity { Insensitive, Sensitive };
…
};
tabWidget->setCornerWidget(widget, Qt::TopLeft);
str.indexOf("$(QTDIR)", Qt::Insensitive);
最後の行でInsensitive は何を意味するのでしょう我々の enum 型の命名のガイドラインではそれぞれの enum 値で enum の型名の少なくとも1つの部分を繰り返すことになっています
namespace Qt
{
enum Corner { TopLeftCorner, BottomRightCorner, … };
enum CaseSensitivity { CaseInsensitive,
CaseSensitive };
…
};
tabWidget->setCornerWidget(widget, Qt::TopLeftCorner);
str.indexOf("$(QTDIR)", Qt::CaseInsensitive);
enum の値を OR でまとめてフラグとして使用できる場合その結果は int に格納するのが伝統的な方法ですがこれは型安全ではありませんQt 4 では T  enum 型である QFlags&amp;amp;lt;T&amp;amp;gt; というテンプレートクラスを導入しましたQt では利便性を考えこのようなフラグ型の名前を typedef してあるためQFlags&amp;amp;lt;Qt::AlignmentFlag&amp;amp;gt; の代わりに Qt::Alignment と書くことができます

慣例的にenum 型の名前には(一度に1つのフラグしか持たないため)単数形を使用していて"flags" 型は複数形の名前にしてあります:
enum RectangleEdge { LeftEdge, RightEdge, … };
typedef QFlags&amp;lt;RectangleEdge&amp;gt; RectangleEdges;
"flags" 型の名前が単数形の場合もありますこの場合enum 型は Flag で終わる名前にしています
enum AlignmentFlag { AlignLeft, AlignTop, … };
typedef QFlags&amp;lt;AlignmentFlag&amp;gt; Alignment;
h3. 関数と引数の命名方法

関数の命名の第一のルールは名前から副作用の有無が分かるようにすることですQt 3 では const 関数 QString::simplifyWhiteSpace() がこの規則を違反していましたこの関数は名前の通り呼び出された文字列自体を変更するのではなくQString を返していたからですこの関数は Qt 4 では QString::simplified() という名前に変更されています

引数の名前はその API を使用するコードには現れませんがプログラマーにとっては重要な意味を持ちます最近の IDE ではプログラマーがコードを書いている際に表示されることが多いため引き数に適切な名前をつけヘッダファイルと同じ名前をドキュメントでも使用することはとても大事なことです

h3. bool 型の取得関数設定関数プロパティの命名方法

bool 型のプロパティに対する取得関数と設定関数の良い名前を探すことは常に難しい問題です取得関数は checked() とすべきでしょうかそれとも isChecked() とすべきでしょうかscrollBarsEnabled()  areScrollBarEnabled() ではどうでしょうか

Qt 4 では取得関数の命名に以下のガイドラインを使用しました

''' 形容詞は接頭語 is をつける
''' isChecked()
'''''' isDown()
'''''' isEmpty()
'''''' isMovingEnabled()

* 複数形の名詞に対する形容詞の場合は is をつけない
** scrollBarsEnabled() でありareScrollBarsEnabled() ではない

* 動詞の場合は接頭語をつけず原型にする(三単現の s はつけない)
** acceptDrops() でありacceptsDrops() ではない
** allColumnsShowFocus()

* 名詞の場合は通常は接頭語をつけない
** autoCompletion() でありisAutoCompletion() ではない
** boundaryChecking()

* 接頭語をつけないことが適切ではない場合にはis をつける
** isOpenGLAvailable() でありopenGL() ではない
** isDialog() でありdialog() ではない

(dialog() という名前の関数があった場合 何かしらの QDialog を返すと思うでしょう)

設定関数の名前は取得関数から接頭語を除いて名前の最初に set をつけています(: setDown()  setScrollBarsEnabled()) プロパティの名前は取得関数から接頭語を除いたものです

== 一般的な罠を回避する ==

=== 利便性の罠 ===

つの間違ったコンセプトは何かをするために必要なコードが短ければ短いほど良い API であるということですコードが書かれるが1度であったとしてもそのコードは何度も何度も読まれます
QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical,
0, "volume");
これは以下に比べてとても読みにくい(だけではなく書きにくい)でしょう
QSlider slider = new QSlider(Qt::Vertical);
slider->setRange(12, 18);
slider->setPageStep(3);
slider->setValue(13);
slider->setObjectName("volume");
h3. bool 型の引数の罠

bool 型の引数は頻繁に理解しづらいコードにつながります特に既存の関数に bool 型の引数を追加するのはほぼ例外なく間違いですこれにあてはまる Qt での例は repaint() この関数は背景を消去するかどうかの指定のための bool 型の引数をオプションでとります(デフォルトは true)これにより以下のようなコードが書かれます
widget->repaint(false);
初心者はこれを見て "再描画はされない" と思うかもしれません

無駄な拡張を防ぐために関数を1つ追加する代わりに bool 型の引数を追加したということは明らかですが逆に無駄な拡張をもたらしていますQt ユーザーの中のどのくらいの人が以下の3つの行の本当の意味での違いを知っているでしょうか
widget->repaint();
widget->repaint(true);
widget->repaint(false);
API を以下のようにすることでいくらか改善されるでしょう
widget->repaint();
widget->repaintWithoutErasing();
Qt 4 ではシンプルにウィジェットの背景を消去せずに描画するという選択自体を削除しましたQt 4 ではダブルバッファリングをフレームワークとしてサポートしたためこの機能は無くなりました

さらにいくつかの例を示します
widget->setSizePolicy(QSizePolicy::Fixed,
QSizePolicy::Expanding, true);
textEdit->insert("Where's Waldo?", true, true, false);
QRegExp rx("moc_.c??", false, true);
つの代替案は bool 型の引数を enum 型に置き換えることですQt 4 では QString の大文字小文字の区別に対してこれを適用しました以下の2行を比較してみて下さい
str.replace("USER", user, false); // Qt 3
str.replace("USER", user, Qt::CaseInsensitive); // Qt 4
=== ものまねの罠 ===

== ケーススタディ ==

=== QProgressBar ===

このコンセプトを実際の例で見てみましょうQProgressBar  API  Qt 3  Qt 4 で比較してみますQt 3 では以下のようになっていました
class QProgressBar : public QWidget
{
…
public:
int totalSteps() const;
int progress() const;

const QString &progressString;() const;

bool percentageVisible() const;
void setPercentageVisible(bool);

void setCenterIndicator(bool on);

bool centerIndicator() const;

void setIndicatorFollowsStyle(bool);

bool indicatorFollowsStyle() const;

public slots:

void reset();
virtual void setTotalSteps(int totalSteps);
virtual void setProgress(int progress);
void setProgress(int progress, int totalSteps);

protected:

virtual bool setIndicator(QString &progressStr;,
int progress,
int totalSteps);
…
};
この API はとても複雑で一貫性に欠けています例えば名前からは reset()  setTotalSteps()setProgress() が緊密に連携していることは読み取れません

この API を改善する上でカギとなる点はQProgressBar  Qt 4  QAbstractSpinBox クラスやその派生クラスの QSpinBoxQSliderQDialog に似ているということです解決方法は progress  totalSteps  minimum  maximumvalue で置き換えvalueChanged() シグナルを追加しsetRange() 関数を利便性を考えて追加することです

次のポイントはprogressString  percentageindicator が同じものを指していることですこれは実際はプログレスバー上に表示されているテキストになります通常はこのテキストはパーセントですがsetIndicator() 関数を使用することで様々なものが設定できます新しい API は以下のようになります
virtual QString text() const;
void setTextVisible(bool visible);
bool isTextVisible() const;
デフォルトではこのテキストはパーセントの表示ですこれは text() を再実装することで変更可能です

Qt 3  API にあった setCenterIndicator()  setIndicatorFollowsStyle() 関数はアライメントに関するものですこれらは setAlignment() という1つの関数に変更しました
void setAlignment(Qt::Alignment alignment);
プログラマーが setAlignment() を実行しない場合はアライメントはスタイルによって決まりますMotif をベースにしたスタイルではテキストは中央に表示されその他のスタイルでは右側に表示されます

改善後の QProgressBar  API は以下の通りです
class QProgressBar : public QWidget
{
…
public:
void setMinimum(int minimum);
int minimum() const;
void setMaximum(int maximum);
int maximum() const;
void setRange(int minimum, int maximum);
int value() const;

virtual QString text() const;

void setTextVisible(bool visible);
bool isTextVisible() const;
Qt::Alignment alignment() const;
void setAlignment(Qt::Alignment alignment);

public slots:

void reset();
void setValue(int value);

signals:

void valueChanged(int value);
…
};
=== QAbstractPrintDialog  QAbstractPageSizeDialog ===

Qt 4.0 では QAbstractPrintDialog  QAbstractPageSizeDialog の2つのクラスが登場しそれぞれ QPrintDialog  QPageSizeDialog の基底クラスとなっていますこれはQAbstractPrint-  -PageSizeDialog のポインタを引数で取り処理を行う Qt  API がないため完全に無意味でしたqdoc をトリッキーに使用し我々はこれらを隠しましたがこれらは不必要な抽象クラスの典型的な例でした

これは ''良い'' 抽象化が間違っているということではありませんがファクトリなどの仕組みで QPrintDialog を変更する方法を用意すべきでした。#ifdef QTOPIA_PRINTDIALOG というマクロが宣言されているのがなによりの証拠です

h3. QAbstractItemModel

Qt 4 のモデルビューの問題の詳細については様々なところで述べられていますが一般化した教訓としては、“QAbstractFoo 考えうる全ての派生クラスから単に和集合をとって作ればよいわけではないということですこのような "すべての可能性を抽象化" した基底クラスは決して良い解決法ではないということですQAbstractItemModel はこの間違いを犯しましたこのクラスは QTreeOfTablesModel であり結果的に API が複雑になりこれが ''すべての素晴らしい派生クラスに継承されています'' 

単に抽象化をしても API が自動的に良くなるわけではありません

h3. QLayoutIterator  QGLayoutIterator

Qt 3 でカスタムレイアウトを作成するには QLayout  QGLayoutIterator("G"  generic からきています)の両方の派生クラスが必要でしたQGLayoutIterator の派生クラスのインスタンスのポインタは QLayoutIterator をラップしていたためユーザーは他のイテレータと同様に使用することができましたQLayoutIterator により以下のようなコードを書くことができました
QLayoutIterator it = layout()->iterator();
QLayoutItem **child;
while ((child = it.current()) != 0) {
if (child->widget() == myWidget) {
it.takeCurrent();
return;
}
++it;
}

Qt 4 では QGLayoutIterator クラス(と内部の Box と Grid の派生クラス)をなくし、QLayout の派生クラスで itemAt()、takeAt()、count() を実装するようにしました。

QImageSink

Qt 3 には画像を順番に読み込みアニメーションをするための一連のクラスがありました。QImageSource/Sink/QASyncIO/QASyncImageIO クラスです。しかし、これらを使用するのは QLabel でのアニメーションのみであったため、これらは結局やりすぎでした。

ここから学んだことは、なんらかの漠然とした将来の可能性のために抽象化をするのは良くないということでした。はじめはシンプルにしましょう。そのような未来が来た場合でも、変更するシステムは複雑なものよりシンプルな方が対応も簡単でしょう。

Qt 3 vs. Qt 4 その他?

QWidget::setWindowModified(bool)

Q3URL vs. QUrl

Q3TextEdit vs. QTextEdit

どのように仮想関数を無くしたのか

Qt のクリッピングの物語

クリップの矩形を設定した場合、実際は領域(setClipRect() の代わりに setClipRegion(QRect) であるべき)を設定していること。