Trả về đối tượng “NULL” nếu không tìm thấy kết quả tìm kiếm


94

Tôi khá mới với C ++ nên tôi có xu hướng thiết kế với rất nhiều Java-isms trong khi học. Dù sao, trong Java, nếu tôi có lớp với phương thức 'tìm kiếm' sẽ trả về một đối tượng Ttừ một Collection< T >tham số phù hợp với một tham số cụ thể, tôi sẽ trả về đối tượng đó và nếu đối tượng không được tìm thấy trong bộ sưu tập, tôi sẽ trả về null. Sau đó, trong chức năng gọi của tôi, tôi sẽ chỉ kiểm traif(tResult != null) { ... }

Trong C ++, tôi phát hiện ra rằng tôi không thể trả về một nullgiá trị nếu đối tượng không tồn tại. Tôi chỉ muốn trả về một 'chỉ báo' kiểu T thông báo cho hàm đang gọi rằng không có đối tượng nào được tìm thấy. Tôi không muốn đưa ra một ngoại lệ vì nó không thực sự là một trường hợp ngoại lệ.

Đây là mã của tôi trông giống như ngay bây giờ:

class Node {
    Attr& getAttribute(const string& attribute_name) const {
       //search collection
       //if found at i
            return attributes[i];
       //if not found
            return NULL; // what should this be?
    }

private:
    vector<Attr> attributes;
}

Làm cách nào để thay đổi nó để tôi có thể cung cấp loại điểm đánh dấu đó?


6
Ngoại lệ và NULL không phải lúc nào cũng là giải pháp duy nhất. Bạn thường có thể chọn một giá trị để trả về cho biết không tìm thấy: ví dụ: std::find(first, last, value)trả về lastnếu không có phần tử nào khớp.
Cascabel

Câu trả lời:


70

Trong C ++, các tham chiếu không được rỗng. Nếu bạn muốn tùy chọn trả về null nếu không tìm thấy gì, bạn cần trả về một con trỏ, không phải một tham chiếu:

Attr *getAttribute(const string& attribute_name) const {
   //search collection
   //if found at i
        return &attributes[i];
   //if not found
        return nullptr;
}

Ngược lại, nếu bạn khăng khăng muốn trả về bằng tham chiếu, thì bạn nên ném một ngoại lệ nếu không tìm thấy thuộc tính.

(Nhân tiện, tôi hơi lo lắng về việc phương thức của bạn đang tồn tại constvà trả về một constthuộc tính không phải là thuộc tính. Vì lý do triết học, tôi khuyên bạn nên trả lại const Attr *. Nếu bạn cũng có thể muốn sửa đổi thuộc tính này, bạn có thể nạp chồng bằng một constphương thức không phải cũng trả về một constthuộc tính không phải .)


2
Cảm ơn. Nhân tiện, đây có phải là một cách được chấp nhận để thiết kế một quy trình như vậy không?
aduric

6
@aduric: Vâng. Tham chiếu có nghĩa là kết quả phải tồn tại. Con trỏ ngụ ý kết quả có thể không tồn tại.
Hóa đơn

7
Chỉ tò mò, chúng ta sẽ trở lại nullptrthay vì NULLcho c ++ 11 bây giờ?
Quang phổ

1
có luôn luôn sử dụng nullptr trên NULL trong C ++ 11 trở lên. nếu bạn cần phải tương thích ngược với các phiên bản earliver sau đó không
Conrad Jones

56

Có một số câu trả lời có thể có ở đây. Bạn muốn trả lại một cái gì đó có thể tồn tại. Dưới đây là một số tùy chọn, từ ít ưu tiên nhất đến ưu tiên nhất:

  • Trở lại bằng cách tham khảo, và tín hiệu không thể tìm thấy bởi ngoại lệ.

    Attr& getAttribute(const string& attribute_name) const 
    {
       //search collection
       //if found at i
            return attributes[i];
       //if not found
            throw no_such_attribute_error;
    }

Có khả năng không tìm thấy các thuộc tính là một phần bình thường của quá trình thực thi và do đó không quá đặc biệt. Việc xử lý điều này sẽ rất ồn ào. Không thể trả về giá trị null vì hành vi không xác định có tham chiếu null.

  • Quay lại bằng con trỏ

    Attr* getAttribute(const string& attribute_name) const 
    {
       //search collection
       //if found at i
            return &attributes[i];
       //if not found
            return nullptr;
    }

Thật dễ dàng quên kiểm tra xem một kết quả từ getAttribute có phải là một con trỏ không phải NULL hay không và là một nguồn dễ phát sinh lỗi.

  • Sử dụng Boost.Optional

    boost::optional<Attr&> getAttribute(const string& attribute_name) const 
    {
       //search collection
       //if found at i
            return attributes[i];
       //if not found
            return boost::optional<Attr&>();
    }

Một boost :: tùy chọn biểu thị chính xác những gì đang diễn ra ở đây và có các phương pháp dễ dàng để kiểm tra xem một thuộc tính như vậy có được tìm thấy hay không.


Lưu ý phụ: std :: option gần đây đã được bình chọn thành C ++ 17, vì vậy đây sẽ là một thứ "tiêu chuẩn" trong tương lai gần.


+1 Tôi sẽ chỉ đề cập đến tăng :: tùy chọn trước và chỉ đề cập ngắn gọn đến các lựa chọn thay thế khác.
Nemanja Trifunovic

Ya Tôi đã thấy boost :: tùy chọn được đề cập ở đâu đó nhưng tôi nghĩ rằng nó đòi hỏi quá nhiều chi phí. Nếu sử dụng nó là cách tiếp cận tốt nhất cho những loại vấn đề này, tôi sẽ bắt đầu sử dụng nó.
aduric

boost::optionalkhông liên quan đến nhiều chi phí (không phân bổ động), đó là lý do tại sao nó rất tuyệt vời. Sử dụng nó với các giá trị đa hình đòi hỏi phải bao bọc các tham chiếu hoặc con trỏ.
Matthieu M.

2
@MatthieuM. Có vẻ như aduric chi phí đề cập đến không phải là hiệu suất, mà là chi phí của việc đưa một thư viện bên ngoài vào dự án.
Swoogan

Một phụ lục cho câu trả lời của tôi: hãy lưu ý rằng có một chuyển động đang diễn ra để chuẩn hóa tùy chọn như một thành phần std, có thể cho những gì cũng có thể là C ++ 17. Vì vậy, bạn nên biết về kỹ thuật này.
Kaz Dragon

22

Bạn có thể dễ dàng tạo một đối tượng tĩnh đại diện cho trả về NULL.

class Attr;
extern Attr AttrNull;

class Node { 
.... 

Attr& getAttribute(const string& attribute_name) const { 
   //search collection 
   //if found at i 
        return attributes[i]; 
   //if not found 
        return AttrNull; 
} 

bool IsNull(const Attr& test) const {
    return &test == &AttrNull;
}

 private: 
   vector<Attr> attributes; 
};

Và ở đâu đó trong tệp nguồn:

static Attr AttrNull;

NodeNull không nên thuộc loại Attr?
aduric


2

Như bạn đã tìm ra rằng bạn không thể làm điều đó theo cách bạn đã làm trong Java (hoặc C #). Đây là một gợi ý khác, bạn có thể chuyển tham chiếu của đối tượng làm đối số và trả về giá trị bool. Nếu kết quả được tìm thấy trong bộ sưu tập của bạn, bạn có thể gán nó cho tham chiếu đang được chuyển và trả về 'true', nếu không trả về 'false'. Vui lòng xem xét mã này.

typedef std::map<string, Operator> OPERATORS_MAP;

bool OperatorList::tryGetOperator(string token, Operator& op)
{
    bool val = false;

    OPERATORS_MAP::iterator it = m_operators.find(token);
    if (it != m_operators.end())
    {
        op = it->second;
        val = true;
    }
    return val;
}

Hàm trên phải tìm Toán tử dựa vào khóa 'mã thông báo', nếu nó tìm thấy mã, nó sẽ trả về true và gán giá trị cho tham số Operator & op.

Mã người gọi cho quy trình này trông như thế này

Operator opr;
if (OperatorList::tryGetOperator(strOperator, opr))
{
    //Do something here if true is returned.
}

1

Lý do mà bạn không thể trả về NULL ở đây là vì bạn đã khai báo kiểu trả về của mình là Attr&. Dấu cuối &làm cho giá trị trả về trở thành "tham chiếu", về cơ bản là một con trỏ được bảo đảm-không-phải-null tới một đối tượng hiện có. Nếu bạn muốn có thể trả về null, hãy thay đổi Attr&thành Attr*.


0

Bạn không thể trả về NULLvì kiểu trả về của hàm là một đối tượng referencechứ không phải a pointer.


-3

Bạn có thể thử điều này:

return &Type();

6
Mặc dù đoạn mã này có thể giải quyết câu hỏi, bao gồm một lời giải thích thực sự giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho người đọc trong tương lai và những người đó có thể không biết lý do cho đề xuất mã của bạn.
NathanOliver

Điều này có thể trả về một tham chiếu chết cho một đối tượng trên ngăn xếp phương thức, phải không?
mpromonet
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.