Qt QList::replace()で学ぶリスト操作:実践的なコード例と活用術
QList::replace()
とは何か
QList::replace()
は、Qt のコンテナクラスである QList
が提供するメンバ関数で、リスト内の特定の位置にある要素を新しい要素で置き換えるために使用されます。
シグネチャ
通常、以下のようなシグネチャを持っています。
void QList::replace(int i, const T &value)
const T &value
: 置き換える新しい値です。T
はQList
が格納している要素の型を示します。int i
: 置き換えたい要素のインデックスを指定します。インデックスは0から始まります。
動作
QList::replace(int i, const T &value)
は、以下の動作をします。
- 指定されたインデックス
i
の位置に要素が存在するかどうかを確認します。 - もし
i
が有効なインデックス(つまり、0 <= i < size()
)であれば、その位置にある既存の要素をvalue
で上書きします。 - もし
i
が有効なインデックスではない場合(例えば、リストのサイズを超えるインデックスが指定された場合)、この関数は何もせず、クラッシュすることはありません。ただし、通常は有効なインデックスを指定することが期待されます。
使用例
#include <QCoreApplication>
#include <QList>
#include <QDebug> // デバッグ出力用
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QList<QString> myList;
myList << "Apple" << "Banana" << "Cherry" << "Date";
qDebug() << "元のリスト:" << myList; // 出力: ("Apple", "Banana", "Cherry", "Date")
// インデックス1の要素("Banana")を"Blueberry"に置き換える
myList.replace(1, "Blueberry");
qDebug() << "置き換え後 (インデックス1):" << myList; // 出力: ("Apple", "Blueberry", "Cherry", "Date")
// インデックス3の要素("Date")を"Elderberry"に置き換える
myList.replace(3, "Elderberry");
qDebug() << "置き換え後 (インデックス3):" << myList; // 出力: ("Apple", "Blueberry", "Cherry", "Elderberry")
// 無効なインデックスを指定した場合(何も変更されない)
myList.replace(10, "Fig");
qDebug() << "置き換え後 (無効なインデックス):" << myList; // 出力: ("Apple", "Blueberry", "Cherry", "Elderberry") (変更なし)
return a.exec();
}
この例では、myList
という QString
の QList
を作成し、replace()
を使って特定の要素を新しい文字列に置き換えています。無効なインデックスを指定した場合に、リストが変更されないことも確認できます。
QList::replace()
は、既存の要素を置き換えるための明示的なメソッドですが、要素へのアクセスと変更には operator[]
(角括弧演算子) もよく使われます。
// replace() を使用
myList.replace(1, "Blueberry");
// operator[] を使用
myList[1] = "Blueberry"; // もしくは myList.operator[](1) = "Blueberry";
これら2つの方法は、既存の要素を置き換えるという点では同じ結果をもたらします。しかし、使い分けの視点から見ると以下の点が挙げられます。
- 例外安全性(インデックスエラー):
replace()
は、無効なインデックスが指定されてもプログラムはクラッシュしません(変更が適用されないだけです)。operator[]
は、デバッグビルドではアサート(assertion)によってクラッシュする可能性があり、リリースビルドでは未定義動作を引き起こす可能性があります。そのため、operator[]
を使用する場合は、インデックスが有効であることを事前に確認する(i < myList.size()
など)必要があります。
- 明示性:
replace()
は「置き換える」という意図がより明確に伝わります。
QList::replace()
は比較的シンプルな関数ですが、その性質上、いくつかの一般的な問題に遭遇することがあります。
インデックスの範囲外アクセス (Index Out Of Range)
エラーの状況
QList::replace(int i, const T &value)
を呼び出す際に、i
に指定するインデックスがリストの有効な範囲(0
から size() - 1
)を超えている場合。replace()
自体はクラッシュしませんが、意図した動作にならない、またはデバッグビルドでQtのアサート(Q_ASSERT
)がトリガーされてプログラムが停止する場合があります。
具体的な現象
- リリースビルドでは、未定義動作(予期せぬデータの破壊やプログラムのクラッシュ)が発生する可能性がある。
- デバッグコンソールに「
ASSERT failure in QList::at: "index out of range"
」のようなメッセージが表示され、プログラムが停止する。 - リストの内容が更新されない。
トラブルシューティング
-
リストのサイズ確認
replace()
を呼び出す前に、リストが空でないことを確認します。QList<QString> emptyList; // emptyList.replace(0, "Test"); // これは範囲外エラーの原因になる if (!emptyList.isEmpty()) { emptyList.replace(0, "Test"); } else { qDebug() << "リストが空のため置き換えできません。"; }
-
インデックスの検証
replace()
を呼び出す前に、常にインデックスが有効な範囲内にあることを確認します。QList<int> myList = {10, 20, 30}; int indexToReplace = 5; // 無効なインデックス if (indexToReplace >= 0 && indexToReplace < myList.size()) { myList.replace(indexToReplace, 99); } else { qDebug() << "エラー: インデックスが範囲外です。" << indexToReplace; }
オブジェクトのコピーとポインタの問題 (Copy vs. Pointer Issues)
エラーの状況
QList
がオブジェクトのポインタではなく、オブジェクトそのものを格納している場合に発生しやすい問題です。特に、リスト内のオブジェクトを別の方法で参照して変更しようとしたり、リストから取り出した要素を直接変更しようとしたりする場合に、リスト内の実際の要素が更新されないことがあります。
具体的な現象
QList
にオブジェクトそのものを格納している場合、QList::value(i)
で要素を取得し、その取得した要素を変更しても、元のリスト内の要素は変更されない。これはvalue()
が要素のコピーを返すためです。
トラブルシューティング
- 参照またはポインタを使用する
-
replace()
を使用して新しい要素を代入する。QList<MyObject> myObjects; // ... myObjects に MyObject のインスタンスを追加 ... MyObject newObject; // ... newObject のプロパティを設定 ... // インデックス 0 の要素を新しい MyObject で置き換える myObjects.replace(0, newObject);
-
operator[]
を使用して参照を取得し、その参照経由で要素を変更する。これは既存の要素のプロパティを変更したい場合に特に有効です。QList<MyObject> myObjects; myObjects.append(MyObject("Original")); // MyObject に適切なコンストラクタがあると仮定 // インデックス0のMyObjectの参照を取得し、その参照を通じてプロパティを変更する if (!myObjects.isEmpty()) { myObjects[0].setName("Modified"); // MyObjectにsetName()があると仮定 }
-
QList
にポインタを格納することを検討する。QList<MyObject*>
のようにポインタを格納すると、リストから取り出したポインタを介してオブジェクトを直接変更できます。ただし、その場合はメモリ管理(new
とdelete
)に注意が必要です。QList<MyObject*> myObjectPointers; myObjectPointers.append(new MyObject("Original Pointer")); // ポインタを介してオブジェクトのプロパティを変更 if (!myObjectPointers.isEmpty()) { myObjectPointers.at(0)->setName("Modified Pointer"); // あるいは myObjectPointers[0]->setName("Modified Pointer"); } // リストが不要になったら、要素のメモリを解放するのを忘れない qDeleteAll(myObjectPointers); myObjectPointers.clear();
-
不適切な型の代入 (Type Mismatch)
エラーの状況
replace()
の第二引数に、QList
が格納するように宣言されている型と異なる型の値を渡そうとする場合。
具体的な現象
- コンパイルエラーが発生する。メッセージは「
cannot convert 'X' to 'Y'
」のような形になることが多いです。
トラブルシューティング
-
型の整合性を確認
QList<T>
のT
と、replace()
に渡すvalue
の型が一致しているか、または暗黙的に変換可能であるかを確認します。必要に応じてキャストを行うか、適切な型の値を渡します。QList<int> intList; intList << 1 << 2 << 3; // intList.replace(0, "Hello"); // コンパイルエラー: QStringはintに変換できない intList.replace(0, 5); // OK
暗黙的共有とデタッチの理解不足 (Implicit Sharing and Detach)
エラーの状況
Qtのコンテナクラス(QList
、QString
、QImage
など)は、**暗黙的共有(Implicit Sharing)**という最適化メカニズムを使用しています。これは、コンテナがコピーされたときに、実際にデータがコピーされるのではなく、同じデータを参照するようになるというものです。データが変更されたときに初めて("detach" されて)データのコピーが作成されます。
QList::replace()
は要素を変更する操作なので、通常はリストのデータがデタッチされますが、まれにこのデタッチのタイミングがコードの他の部分と予期せぬ相互作用を引き起こすことがあります。
具体的な現象
- イテレータを使用中にリストを変更すると、イテレータが無効になることがある。
QList
のコピーに対してreplace()
を呼び出しても、元のリストが変更されない。
トラブルシューティング
-
イテレータの再取得
QList
を変更する操作(replace()
も含む)を行うと、そのリストに対する既存のイテレータが無効になる可能性があります。リストを変更した後は、再度イテレータを取得し直すようにします。QList<int> data = {1, 2, 3}; QList<int>::iterator it = data.begin(); // 最初の要素を置き換える data.replace(0, 10); // この時点で 'it' は無効になっている可能性がある。 // 再度イテレータを取得するか、イテレータを維持できる安全な操作のみを行う it = data.begin(); // 再取得 qDebug() << *it;
-
foreach ループ内での変更の注意
foreach
ループはコンテナのコピーに対して動作するため、ループ内でQList::replace()
を呼び出しても、ループの外にある元のリストは変更されません。リストの内容を変更したい場合は、通常のC++11の範囲ベースforループや、インデックスベースのループを使用します。QList<int> numbers = {1, 2, 3}; // 悪い例: foreach はコピーに対して動作するため、元の numbers は変更されない // for (int num : numbers) { // Qt5以前のforeachは別の構文 // if (num == 2) { // numbers.replace(numbers.indexOf(2), 20); // これは元の numbers に影響しない // } // } // 良い例: インデックスベースのループ for (int i = 0; i < numbers.size(); ++i) { if (numbers.at(i) == 2) { numbers.replace(i, 20); // numbers の内容が変更される } } qDebug() << numbers; // (1, 20, 3)
QList::replace()
は、既存の要素を新しい要素で置き換えるための非常に便利な関数です。ここでは、いくつかの異なるシナリオでその使い方を見ていきましょう。
例 1: 基本的な数値の置き換え
最も基本的な使用例です。リスト内の特定のインデックスにある数値を別の数値に置き換えます。
#include <QCoreApplication>
#include <QList>
#include <QDebug> // デバッグ出力用
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QList<int> numbers;
numbers << 10 << 20 << 30 << 40 << 50;
qDebug() << "元のリスト:" << numbers;
// 出力: 元のリスト: QList(10, 20, 30, 40, 50)
// インデックス2の要素(30)を99に置き換える
numbers.replace(2, 99);
qDebug() << "インデックス2を置き換え後:" << numbers;
// 出力: インデックス2を置き換え後: QList(10, 20, 99, 40, 50)
// インデックス0の要素(10)を5に置き換える
numbers.replace(0, 5);
qDebug() << "インデックス0を置き換え後:" << numbers;
// 出力: インデックス0を置き換え後: QList(5, 20, 99, 40, 50)
// リストの最後の要素(インデックス size() - 1)を置き換える
if (!numbers.isEmpty()) {
numbers.replace(numbers.size() - 1, 100);
qDebug() << "最後の要素を置き換え後:" << numbers;
// 出力: 最後の要素を置き換え後: QList(5, 20, 99, 40, 100)
}
return a.exec();
}
解説
- 最後の要素を置き換える際には、
numbers.size() - 1
をインデックスとして使用します。isEmpty()
でリストが空でないことを確認するのは、より安全なプログラミング習慣です。 numbers.replace(2, 99);
は、numbers
の3番目の要素(インデックス2)を99
に変更します。numbers << 10 << 20;
のように、operator<<
を使って簡単に要素を追加できます。
例 2: 文字列(QString
)の置き換えとインデックスの検証
文字列を格納する QList<QString>
での置き換えと、インデックスが有効かどうかをチェックするコードを含めます。
#include <QCoreApplication>
#include <QList>
#include <QString>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QList<QString> fruits;
fruits << "Apple" << "Banana" << "Cherry" << "Date";
qDebug() << "元のフルーツリスト:" << fruits;
// 出力: 元のフルーツリスト: QList("Apple", "Banana", "Cherry", "Date")
// 有効なインデックスで置き換え
int validIndex = 1; // "Banana" のインデックス
if (validIndex >= 0 && validIndex < fruits.size()) {
fruits.replace(validIndex, "Blueberry");
qDebug() << "インデックス" << validIndex << "を置き換え後:" << fruits;
// 出力: インデックス 1 を置き換え後: QList("Apple", "Blueberry", "Cherry", "Date")
} else {
qDebug() << "エラー: 無効なインデックス:" << validIndex;
}
// 無効なインデックスで置き換えを試みる
int invalidIndex = 5; // リストの範囲外
if (invalidIndex >= 0 && invalidIndex < fruits.size()) {
fruits.replace(invalidIndex, "Grape");
qDebug() << "インデックス" << invalidIndex << "を置き換え後:" << fruits;
} else {
qDebug() << "エラー: 無効なインデックス:" << invalidIndex;
// 出力: エラー: 無効なインデックス: 5
}
// 特定の値を探して置き換える(indexOfとreplaceの組み合わせ)
int indexOfCherry = fruits.indexOf("Cherry");
if (indexOfCherry != -1) { // -1 は見つからなかったことを示す
fruits.replace(indexOfCherry, "Cranberry");
qDebug() << "CherryをCranberryに置き換え後:" << fruits;
// 出力: CherryをCranberryに置き換え後: QList("Apple", "Blueberry", "Cranberry", "Date")
} else {
qDebug() << "Cherryは見つかりませんでした。";
}
return a.exec();
}
解説
fruits.indexOf("Cherry")
は、指定した値がリスト内で最初に出現するインデックスを返します。見つからない場合は-1
を返します。これとreplace()
を組み合わせることで、値ベースの置き換えが可能です。if (validIndex >= 0 && validIndex < fruits.size())
のような条件文で、インデックスが有効な範囲内にあるかを確認することは非常に重要です。これにより、プログラムのクラッシュや予期せぬ動作を防ぐことができます。
例 3: カスタムクラスのオブジェクトの置き換え
QList
にカスタムクラスのオブジェクトを格納し、それを replace()
で置き換える例です。カスタムクラスが QList
に格納されるためには、コピー可能である必要があります(つまり、コピーコンストラクタと代入演算子を持つ)。
#include <QCoreApplication>
#include <QList>
#include <QString>
#include <QDebug>
// カスタムクラスの定義
class Book
{
public:
Book(const QString &title = "", const QString &author = "", int pages = 0)
: m_title(title), m_author(author), m_pages(pages) {}
// ゲッター
QString title() const { return m_title; }
QString author() const { return m_author; }
int pages() const { return m_pages; }
// QDebug 出力のためのオペレーターオーバーロード
friend QDebug operator<<(QDebug debug, const Book &book)
{
QDebugStateSaver saver(debug);
debug.nospace() << "Book(Title:\"" << book.m_title
<< "\", Author:\"" << book.m_author
<< "\", Pages:" << book.m_pages << ")";
return debug;
}
private:
QString m_title;
QString m_author;
int m_pages;
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QList<Book> library;
library.append(Book("The Hobbit", "J.R.R. Tolkien", 310));
library.append(Book("Pride and Prejudice", "Jane Austen", 279));
library.append(Book("1984", "George Orwell", 328));
qDebug() << "元の図書館リスト:";
for (const Book &book : library) {
qDebug() << book;
}
// 出力例:
// Book(Title:"The Hobbit", Author:"J.R.R. Tolkien", Pages:310)
// Book(Title:"Pride and Prejudice", Author:"Jane Austen", Pages:279)
// Book(Title:"1984", Author:"George Orwell", Pages:328)
// インデックス1のBookオブジェクトを新しいBookオブジェクトで置き換える
Book newBook("To Kill a Mockingbird", "Harper Lee", 281);
int indexToReplace = 1;
if (indexToReplace >= 0 && indexToReplace < library.size()) {
library.replace(indexToReplace, newBook);
qDebug() << "\nインデックス" << indexToReplace << "の書籍を置き換え後:";
for (const Book &book : library) {
qDebug() << book;
}
} else {
qDebug() << "\nエラー: 無効なインデックス:" << indexToReplace;
}
// 出力例:
// インデックス 1 の書籍を置き換え後:
// Book(Title:"The Hobbit", Author:"J.R.R. Tolkien", Pages:310)
// Book(Title:"To Kill a Mockingbird", Author:"Harper Lee", Pages:281)
// Book(Title:"1984", Author:"George Orwell", Pages:328)
return a.exec();
}
library.replace(indexToReplace, newBook);
は、library
内のBook
オブジェクトを、newBook
という新しいBook
オブジェクトのコピーで置き換えます。QList
は要素のコピーを保持するため、渡すオブジェクトのコピーコンストラクタが呼び出されます。QDebug operator<<(QDebug debug, const Book &book)
をオーバーロードすることで、qDebug()
でBook
オブジェクトを直接出力できるようになります。これはデバッグ時に非常に便利です。Book
クラスは、タイトル、著者、ページ数を保持します。
QList::replace()
はリスト内の既存の要素を置き換えるための直接的な方法ですが、同じ目的を達成するための他の方法や、特定のシナリオでより適切かもしれない代替手段がいくつかあります。
QList::operator[] を使用する
最も一般的で直接的な代替手段です。QList
のインデックスアクセス演算子 []
は、指定されたインデックスの要素への非const参照を返します。これにより、その要素を直接変更できます。
特徴
- 安全性に関する注意
operator[]
はインデックスの範囲チェックを行わないため、無効なインデックスにアクセスしようとすると、デバッグビルドではアサート(クラッシュ)し、リリースビルドでは未定義動作を引き起こす可能性があります。 - 直接的な変更
要素のコピーではなく、リスト内の実際の要素への参照を介して変更します。 - 簡潔性
コードが非常に短く読みやすいです。
使用例
#include <QCoreApplication>
#include <QList>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QList<QString> items;
items << "Alpha" << "Beta" << "Gamma";
qDebug() << "元のリスト:" << items; // 出力: ("Alpha", "Beta", "Gamma")
// インデックス1の要素を直接変更
if (1 >= 0 && 1 < items.size()) { // 安全性のためのインデックスチェック
items[1] = "NewBeta";
}
qDebug() << "operator[] で置き換え後:" << items; // 出力: ("Alpha", "NewBeta", "Gamma")
// operator[] を使ってオブジェクトの内部プロパティを変更する例
// (カスタムクラスMyItemがあり、setterを持つと仮定)
/*
QList<MyItem> myItems;
myItems.append(MyItem("Original Name"));
if (0 >= 0 && 0 < myItems.size()) {
myItems[0].setName("Updated Name"); // MyItem の setName メソッドを呼び出す
}
*/
return a.exec();
}
QList::removeAt() と QList::insert() を組み合わせる
これは、既存の要素を削除し、その同じ位置に新しい要素を挿入することで、実質的に置き換えを行う方法です。
特徴
- 使いどころ
リスト内の要素が削除され、その後に全く異なる型の新しい要素が挿入されるなど、より複雑な操作を表現したい場合に適しているかもしれません(ただし、QList
は単一の型しか格納できないため、これはまれなケースです)。 - 効率
単一の要素の置き換えだけであればreplace()
やoperator[]
の方が効率が良いことが多いです。removeAt()
は要素を削除した後に続く要素をシフトさせるオーバーヘッドがあり、insert()
も同様に要素をシフトさせるオーバーヘッドがあります。 - 柔軟性
要素の置き換えだけでなく、要素の削除と挿入という個別の操作を明確に表現できます。
使用例
#include <QCoreApplication>
#include <QList>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QList<double> values;
values << 1.1 << 2.2 << 3.3 << 4.4;
qDebug() << "元のリスト:" << values; // 出力: (1.1, 2.2, 3.3, 4.4)
int indexToModify = 2; // 3.3 のインデックス
if (indexToModify >= 0 && indexToModify < values.size()) {
values.removeAt(indexToModify); // インデックス2の要素を削除 (3.3が削除され、4.4がシフトしてくる)
values.insert(indexToModify, 9.9); // 削除されたインデックス2の位置に9.9を挿入
}
qDebug() << "removeAt/insert で置き換え後:" << values; // 出力: (1.1, 2.2, 9.9, 4.4)
return a.exec();
}
イテレータを使用する (間接的な方法)
QList::iterator
を使用してリストを走査し、目的の要素が見つかったらイテレータを介してその要素の値を変更することも可能です。
特徴
- 直接的な変更
*it = newValue;
のようにデリファレンスしたイテレータに代入することで、リスト内の実際の要素を変更できます。 - 安全性の注意
リストの構造を変更する操作(要素の追加や削除)を行うと、イテレータが無効になる可能性があります。replace()
は構造は変更しませんが、insert()
やremoveAt()
の場合は注意が必要です。 - 汎用性
リスト全体を走査して特定の条件を満たす要素を置き換えたい場合に便利です。
使用例
#include <QCoreApplication>
#include <QList>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QList<char> chars;
chars << 'a' << 'b' << 'c' << 'd';
qDebug() << "元のリスト:" << chars; // 出力: ('a', 'b', 'c', 'd')
// イテレータを使って 'c' を 'X' に置き換える
for (QList<char>::iterator it = chars.begin(); it != chars.end(); ++it) {
if (*it == 'c') {
*it = 'X'; // イテレータが指す要素の値を変更
break; // 最初の 'c' だけを置き換えたい場合は break
}
}
qDebug() << "イテレータで置き換え後:" << chars; // 出力: ('a', 'b', 'X', 'd')
return a.exec();
}
std::replace (C++標準ライブラリ関数、QListでも使用可能)
QList
は std::list
や std::vector
とは異なりますが、そのイテレータとデータ構造の互換性により、C++標準ライブラリのアルゴリズム(<algorithm>
ヘッダ)の一部を QList
に対して直接使用できます。std::replace
は、指定された範囲内で特定の値を持つすべての要素を新しい値で置き換えるために使用できます。
特徴
- 複数の要素に適用
最初の要素だけでなく、すべてのマッチする要素を置き換えます。 - 範囲指定
イテレータペア(開始と終了)で操作範囲を指定できます。 - 値ベースの置き換え
インデックスではなく、値に基づいて複数の要素を置き換えたい場合に非常に便利です。
#include <QCoreApplication>
#include <QList>
#include <QDebug>
#include <algorithm> // std::replace を使用するため
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QList<int> scores;
scores << 85 << 90 << 75 << 90 << 60 << 90;
qDebug() << "元のスコアリスト:" << scores; // 出力: (85, 90, 75, 90, 60, 90)
// リスト内のすべての 90 を 95 に置き換える
std::replace(scores.begin(), scores.end(), 90, 95);
qDebug() << "std::replace で置き換え後:" << scores; // 出力: (85, 95, 75, 95, 60, 95)
// 部分的な範囲の置き換え
scores << 10 << 20 << 10; // リストにさらに要素を追加
qDebug() << "追加後のリスト:" << scores; // 出力: (85, 95, 75, 95, 60, 95, 10, 20, 10)
// 2番目の要素から終わりまでで、10 を 15 に置き換える
std::replace(scores.begin() + 1, scores.end(), 10, 15);
qDebug() << "部分的なstd::replace で置き換え後:" << scores; // 出力: (85, 95, 75, 95, 60, 95, 15, 20, 15)
return a.exec();
}
- イテレータを使ってリストを走査しながら条件に基づいて変更する場合
- イテレータを介した代入 (
*it = newValue;
):リストの構造を変更しない操作(replace()
相当)には適しているが、追加/削除を伴う場合はイテレータの無効化に注意。
- イテレータを介した代入 (
- 要素を削除してから新しい要素を挿入するような、より複雑なロジックが必要な場合
- QList::removeAt() + QList::insert()
明示的なステップで操作を表現。
- QList::removeAt() + QList::insert()
- 値に基づいて複数の要素を置き換えたい場合
- std::replace
最も効率的で推奨される方法。
- std::replace
- 単一の要素をインデックスで置き換えたい場合
- QList::replace()
安全性が高く、意図が明確。 - QList::operator[]
簡潔で高速。ただし、インデックスの範囲チェックは手動で行う必要がある。
- QList::replace()