2010-12-13 20 views
8

Tôi có một struct ++ C và một phương pháp:C++ phương pháp mà có thể/không thể trở về một struct

struct Account 
{ 
    unsigned int id; 
    string username; 
    ... 
}; 


Account GetAccountById(unsigned int id) const { } 

tôi có thể trả về một struct tài khoản nếu tài khoản tồn tại, nhưng phải làm gì nếu không có tài khoản rồi?

tôi nghĩ có:

  • An "có giá trị" cờ trên struct (do đó là một sản phẩm nào có thể được trả lại, với các thiết lập để false)
  • Một bổ sung "có giá trị" con trỏ (const string & id, int * is_ok) được đặt nếu đầu ra hợp lệ
  • Trả lại tài khoản * thay vào đó và trả về con trỏ tới cấu trúc hoặc NULL nếu nó không tồn tại?

Có cách nào tốt nhất để thực hiện việc này không?

Trả lời

13

Bạn quên một rõ ràng nhất, trong C++:

bool GetAccountById(unsigned int id, Account& account); 

Return true và điền vào các tài liệu tham khảo được cung cấp nếu tài khoản tồn tại, nếu không trở false.

Nó cũng có thể được thuận tiện để sử dụng thực tế là con trỏ có thể được null, và có:

bool GetAccountById(unsigned int id, Account* account); 

Điều đó có thể được định nghĩa để trở true nếu id tài khoản tồn tại, nhưng chỉ (tất nhiên) để điền trong tài khoản được cung cấp nếu con trỏ không rỗng. Đôi khi nó tiện dụng để có thể kiểm tra sự tồn tại, và điều này tiết kiệm phải có một phương pháp chuyên dụng cho mục đích đó.

Đó là vấn đề về sở thích của những gì bạn thích.

+1

+1 đánh bại tôi đến một vài giây. :) – casablanca

+3

Tôi rất thích câu trả lời của Juraj bên dưới, bởi vì nó mã hóa tính vô dụng trong kiểu, làm tăng sự an toàn kiểu của mã. Đặc biệt, sử dụng phương pháp của bạn có thể vô tình quên để kiểm tra sự trở lại, trong khi nếu trả lại là giá trị, sau đó bạn phải làm cho nó sử dụng nó :) –

+2

Tôi nghĩ vấn đề chính của tôi ở đây là đặt tên của hàm. GetX() với tôi luôn luôn nên trả về X (nếu nó không có đó là một ngoại lệ). Nếu có một khả năng của X không tồn tại thì sẽ có hàm findX() trả về một trình lặp (hoặc trình lặp như đối tượng). Nếu không thể tìm thấy đối tượng, điều này có thể được xác định từ trình lặp nếu nó được tìm thấy thì trình vòng lặp có thể được chuyển đổi thành tham chiếu của đối tượng.Việc truyền tham số đầu ra không trực quan và yêu cầu bạn có thể tạo một đối tượng không hợp lệ/trống (được chuyển vào và điền). Các đối tượng không hợp lệ/trống là lỗi dễ xảy ra. –

3

Tùy thuộc vào khả năng bạn nghĩ tài khoản không tồn tại sẽ như thế nào.

Nếu nó thực sự đặc biệt - sâu trong ruột của ruột bên trong của hệ thống ngân hàng nơi dữ liệu được cho là hợp lệ - thì có thể ném một ngoại lệ.

Nếu ở cấp giao diện người dùng, xác thực dữ liệu, có thể bạn không ném ngoại lệ.

Trả về một con trỏ có nghĩa là ai đó phải giải quyết bộ nhớ được cấp phát - điều đó phức tạp hơn.

Bạn có thể sử dụng 'ID đánh dấu' (chẳng hạn như 0) để chỉ ra 'tài khoản không hợp lệ' không?

7

Từ các tùy chọn được cung cấp, tôi sẽ trả lại Account*. Nhưng trở về con trỏ có thể có một số tác dụng phụ xấu trên giao diện.

Một khả năng khác là throw ngoại lệ khi không có tài khoản như vậy. Bạn cũng có thể thử boost::optional.

+7

+1 cho 'boost :: optional' –

0

Một cách khác ngoài việc trả về tham chiếu là trả lại con trỏ. Nếu tài khoản tồn tại, hãy trả về con trỏ của nó. Khác, trả về NULL.

2

Tôi sẽ sử dụng Account* và thêm nhận xét tài liệu vào phương pháp cho biết giá trị trả về có thể là NULL.

+1

Nhưng sau đó như Jonathan đã nói, ai đó phải giải phóng con trỏ mà thường dẫn đến rò rỉ bộ nhớ. –

+0

Trừ khi người khác trả về đối tượng được trỏ bởi con trỏ trả về. – Kos

-1

tăng :: tùy chọn có lẽ là cách tốt nhất bạn có thể thực hiện bằng ngôn ngữ nên bị hỏng không có biến thể gốc.

+3

Tôi sẽ nói rằng các ngôn ngữ cố gắng làm tất cả những suy nghĩ của bạn cho bạn là những thứ bị hỏng. –

2

Có một số phương pháp.

1) Ném ngoại lệ. Điều này hữu ích nếu bạn muốn GetAccountById trả lại tài khoản theo giá trị và việc sử dụng ngoại lệ phù hợp với mô hình lập trình của bạn. Một số sẽ cho bạn biết rằng ngoại lệ là "có nghĩa" chỉ được sử dụng trong các trường hợp ngoại lệ. Những thứ như "hết bộ nhớ" hoặc "máy tính đang cháy". Điều này là rất có thể gây tranh cãi, và đối với mọi lập trình viên bạn thấy ai nói ngoại lệ không phải là để kiểm soát dòng chảy, bạn sẽ tìm thấy một người khác (bao gồm cả tôi), người nói rằng ngoại lệ có thể được sử dụng để kiểm soát luồng. Bạn cần phải suy nghĩ về điều này và quyết định cho chính mình.

Account GetAccountById(unsigned int id) const 
{ 
    if(account_not_found) 
    throw std::runtime_error("account not found"); 
} 

2) Không trả lại và Account theo giá trị. Thay vào đó, quay trở lại bởi con trỏ (pointer tốt thông minh), và trở về NULL khi bạn không tìm thấy tài khoản:

boost::shared_ptr<Account> GetAccountById(unsigned int id) const 
{ 
    if(account_not_found) 
    return NULL; 
} 

3) Quay trở lại một đối tượng mà có một 'hiện diện' cờ chỉ ra hay không mục dữ liệu là hiện tại. Boost.Optional là một ví dụ về thiết bị như vậy, nhưng trong trường hợp bạn không thể sử dụng Boost ở đây là đối tượng có khuôn mẫu có thành viên booltrue khi mục dữ liệu có mặt và là false khi không. Bản thân mục dữ liệu được lưu trữ trong thành viên value_. Nó phải được cấu hình mặc định.

template<class Value> 
struct PresenceValue 
{ 
    PresenceValue() : present_(false) {}; 
    PresenceValue(const Value& val) : present_(true), value_(val) {}; 
    PresenceValue(const PresenceValue<Value>& that) : present_(that.present_), value_(that.value_) {}; 
    explicit PresenceValue(Value val) : present_(true), value_(val) {}; 
    template<class Conv> explicit PresenceValue(const Conv& conv) : present_(true), value_(static_cast<Value>(conv)) {}; 
    PresenceValue<Value>& operator=(const PresenceValue<Value>& that) { present_ = that.present_; value_ = that.value_; return * this; } 

    template<class Compare> bool operator==(Compare rhs) const 
    { 
     if(!present_) 
      return false; 
     return rhs == value_; 
    } 
    template<class Compare> bool operator==(const Compare* rhs) const 
    { 
     if(!present_) 
      return false; 
     return rhs == value_; 
    } 
    template<class Compare> bool operator!=(Compare rhs) const { return !operator==(rhs); } 
    template<class Compare> bool operator!=(const Compare* rhs) const { return !operator==(rhs); } 

    bool operator==(const Value& rhs) const { return present_ && value_ == rhs; } 
    operator bool() const { return present_ && static_cast<bool>(value_); } 

    operator Value() const; 

    void Reset() { value_ = Value(); present_ = false; } 

    bool present_; 
    Value value_; 
}; 

Để đơn giản, tôi sẽ tạo ra một typedef cho Account:

typedef PresenceValue<Account> p_account; 

... và sau đó quay trở lại này từ chức năng của bạn:

p_account GetAccountByIf(...) 
{ 
    if(account_found) 
    return p_account(the_account); // this will set 'present_' to true and 'value_' to the account 
    else 
    return p_account(); // this will set 'present_' to false 
} 

Sử dụng này rất đơn giản:

p_account acct = FindAccountById(some_id); 
if(acct.present_) 
{ 
    // magic happens when you found the account 
} 
+0

2) Bạn có nghĩa là 'scoped_ptr' thay vì' shared_ptr'? IMO, nó phải là 'scoped_ptr' hoặc tương đương nếu chúng ta muốn trả về một bản sao hoặc một con trỏ thô nếu chúng ta chỉ trả về một điều khiển cho một' Tài khoản' hiện có. 'Shared_ptr' sẽ chỉ có ý nghĩa với tôi nếu lớp sẽ giữ' Account 'của nó là' shared_ptr 'mà không có quyền sở hữu nghiêm ngặt - và nhiều khả năng các tài khoản đó sẽ được sở hữu một cách nghiêm ngặt bởi một thực thể nào đó. – Kos

+0

3) Có tương đương với 'boost :: optional' thường được đề cập ở đây không? – Kos

+0

@Kos: re 2), uh, có thể. Tôi luôn hiểu sai. :) –

0

Có một cách khác tương tự như mẫu "hợp lệ". Tôi đang phát triển một ứng dụng ngay bây giờ có rất nhiều thứ như vậy trong đó. Nhưng ID của tôi không bao giờ nhỏ hơn 1 (tất cả đều là SERIAL fields trong cơ sở dữ liệu PostgreSQL) vì vậy tôi chỉ có một hàm khởi tạo mặc định cho mỗi cấu trúc (hoặc lớp trong trường hợp của tôi) khởi tạo id với phương thức -1 và isValid() trả về true nếu id không bằng -1. Hoạt động hoàn hảo cho tôi.

0

tôi sẽ làm:

class Bank 
{ 
    public: 

    class Account {}; 
    class AccountRef 
    { 
     public: 
      AccountRef():     m_account(NULL) {} 
      AccountRef(Account const& acc) m_account(&acc) {} 
      bool isValid() const        { return m_account != NULL);} 
      Account const& operator*()      { return *m_account; } 
      operator bool()         { return isValid(); } 
     private: 
      Account const* m_account; 
    }; 
    Account const& GetAccountById(unsigned int id) const 
    { 
     if (id < m_accounts.size()) 
     { return m_accounts[id]; 
     } 
     throw std::outofrangeexception("Invalid account ID"); 
    } 

    AccountRef FindAccountById(unsigned int id) const 
    { 
     if (id < m_accounts.size()) 
     { return AccountRef(m_accounts[id]); 
     } 
     return AccountRef(); 
    } 
    private: 
    std::vector<Account> m_accounts; 
}; 

Một phương pháp được gọi là get nên luôn luôn trở lại (IMHO) các đối tượng yêu cầu. Nếu nó không tồn tại thì đó là một ngoại lệ. Nếu có khả năng một thứ gì đó có thể không tồn tại thì bạn cũng nên cung cấp phương thức tìm kiếm có thể xác định xem đối tượng có tồn tại để người dùng có thể kiểm tra nó hay không.

int main() 
{ 
    Bank Chase; 

    // Get a reference 
    // As the bank ultimately ownes the account. 
    // You just want to manipulate it. 
    Account const& account = Chase.getAccountById(1234); 

    // If there is the possibility the account does not exist then use find() 
    AccountRef ref = Chase.FindAccountById(12345); 
    if (!ref) 
    {  // Report error 
      return 1; 
    } 
    Account const& anotherAccount = *ref; 
} 

Bây giờ tôi có thể đã sử dụng con trỏ thay vì đi đến nỗ lực tạo AccountRef.Vấn đề với điều đó là con trỏ không có quyền sở hữu của symantics và do đó không có dấu hiệu thực sự của ai nên sở hữu (và do đó xóa) con trỏ.

Kết quả là tôi muốn bọc con trỏ trong một số vùng chứa cho phép người dùng thao tác đối tượng chỉ khi tôi muốn chúng. Trong trường hợp này AccountRef không lộ con trỏ để không có cơ hội cho người dùng AccountRef thực sự thử và xóa tài khoản.

Tại đây bạn có thể kiểm tra xem AccountRef có hợp lệ không và trích xuất tham chiếu đến tài khoản (giả sử nó hợp lệ). Bởi vì đối tượng chỉ chứa một con trỏ trình biên dịch có trách nhiệm tối ưu hóa điều này đến mức mà điều này không đắt hơn so với truyền con trỏ xung quanh. Lợi ích là người dùng không thể vô tình lạm dụng những gì tôi đã cung cấp cho họ.

Tóm tắt: AccountRef không có chi phí thời gian thực thực. Tuy nhiên, cung cấp loại an toàn (vì nó ẩn việc sử dụng con trỏ).

0

Tôi thích kết hợp những gì bạn đề xuất với Cờ hợp lệ và những gì người khác đề xuất với mẫu đối tượng null.

Tôi có một lớp cơ sở được gọi là Status mà tôi kế thừa từ trên các đối tượng mà tôi muốn sử dụng làm giá trị trả lại. Tôi sẽ để lại hầu hết nó ra khỏi cuộc thảo luận này vì nó là một chút tham gia nhiều hơn nhưng có vẻ gì đó như thế này

class Status 
{ 
    public: 
    Status(bool isOK=true) : mIsOK(isOK) 
    operator bool() {return mIsOK;} 
    private 
    bool mIsOK 
}; 

bây giờ bạn muốn có

class Account : public Status 
{ 
    public: 
    Account() : Status(false) 
    Account(/*other parameters to initialize an account*/) : ... 
    ... 
}; 

Bây giờ nếu bạn tạo một tài khoản với không thông số:

Account A; 

Không hợp lệ. Nhưng nếu bạn tạo tài khoản có dữ liệu

Account A(id, name, ...); 

Hợp lệ.

Bạn kiểm tra tính hợp lệ của toán tử bool.

Account A=GetAccountByID(id); 
if (!A) 
{ 
    //whoa there! that's an invalid account! 
} 

Tôi làm điều này rất nhiều khi tôi làm việc với các loại toán học. Ví dụ: tôi không muốn phải viết một chức năng giống như thế này

bool Matrix_Multiply(a,b,c); 

trong đó a, b, và c là ma trận. Tôi muốn viết nhiều hơn

c=a*b; 

với quá tải nhà điều hành. Nhưng có những trường hợp a và b không thể nhân lên được vì vậy nó không phải lúc nào cũng hợp lệ. Vì vậy, họ chỉ trả về một c không hợp lệ nếu nó không hoạt động và tôi có thể làm

c=a*b; 
if (!c) //handle the problem. 
Các vấn đề liên quan