Chuỗi hằng tĩnh (thành viên lớp)


444

Tôi muốn có một hằng số tĩnh riêng cho một lớp (trong trường hợp này là một nhà máy hình dạng).

Tôi muốn có một cái gì đó của loại.

class A {
   private:
      static const string RECTANGLE = "rectangle";
}

Thật không may, tôi nhận được tất cả các loại lỗi từ trình biên dịch C ++ (g ++), chẳng hạn như:

ISO C ++ cấm khởi tạo thành viên 'RECTANGLE'

Khởi tạo trong lớp không hợp lệ của thành viên dữ liệu tĩnh thuộc loại không tách rời 'std :: string'

lỗi: làm cho 'RECTANGLE' tĩnh

Điều này cho tôi biết rằng loại thiết kế thành viên này không tuân thủ tiêu chuẩn. Làm thế nào để bạn có một hằng số theo nghĩa đen riêng tư (hoặc có thể là công khai) mà không phải sử dụng chỉ thị #define (tôi muốn tránh sự xấu xí của tính toàn cầu dữ liệu!)

Bất kỳ trợ giúp được đánh giá cao.


15
Cảm ơn tất cả các câu trả lời tuyệt vời của bạn! Sống lâu!
lb

Ai đó có thể vui lòng cho tôi biết loại 'tích phân' là gì không? Cảm ơn rât nhiều.
lb

1
Các loại tích phân đề cập đến các loại đại diện cho số nguyên. Xem publib.boulder.ibm.com/infocenter/comphelp/v8v101/NH
bleater

Chuỗi tĩnh riêng trong nhà máy của bạn không phải là giải pháp tốt - hãy xem xét rằng các máy khách của nhà máy của bạn sẽ phải biết hình dạng nào được hỗ trợ, vì vậy thay vì giữ nó ở chế độ tĩnh riêng tư, hãy đặt chúng vào không gian tên riêng biệt như static const std :: string RECTANGLE = "Hình chữ nhật ".
LukeCodeBaker

nếu lớp của bạn là một lớp mẫu thì hãy xem stackoverflow.com/q/3229883/52074
Trevor Boyd Smith

Câu trả lời:


469

Bạn phải xác định thành viên tĩnh của bạn bên ngoài định nghĩa lớp và cung cấp trình khởi tạo ở đó.

Đầu tiên

// In a header file (if it is in a header file in your case)
class A {   
private:      
  static const string RECTANGLE;
};

và sau đó

// In one of the implementation files
const string A::RECTANGLE = "rectangle";

Cú pháp ban đầu bạn đang cố sử dụng (trình khởi tạo bên trong định nghĩa lớp) chỉ được phép với các kiểu tích phân và enum.


Bắt đầu từ C ++ 17, bạn có một tùy chọn khác, khá giống với khai báo ban đầu của bạn: biến nội tuyến

// In a header file (if it is in a header file in your case)
class A {   
private:      
  inline static const string RECTANGLE = "rectangle";
};

Không có định nghĩa bổ sung là cần thiết.

Hoặc thay vì constbạn có thể khai báo nó constexprtrong biến thể này. Rõ ràng inlinesẽ không còn cần thiết, vì constexprngụ ý inline.


8
Ngoài ra, nếu không có yêu cầu sử dụng chuỗi STL, bạn cũng có thể chỉ cần xác định const char *. (ít chi phí hơn)
KSchmidt

50
Tôi không chắc chắn rằng nó luôn luôn ít chi phí hơn - nó phụ thuộc vào cách sử dụng. Nếu thành viên này được chuyển qua làm đối số cho các hàm lấy chuỗi const &, sẽ có tạm thời được tạo cho mỗi cuộc gọi so với việc tạo một đối tượng chuỗi trong quá trình khởi tạo. Chi phí IMHO để tạo một đối tượng chuỗi tĩnh là không đáng kể.
Tadeusz Kopec

23
Tôi thà sử dụng std :: string luôn luôn. Chi phí không đáng kể, nhưng bạn có nhiều lựa chọn hơn và ít có khả năng viết một số thứ ngu ngốc như "ma thuật" == A :: RECTANGLE chỉ để so sánh địa chỉ của họ ...
Matthieu M.

9
những char const*có sự tốt lành rằng nó được khởi tạo trước khi tất cả khởi động được thực hiện. Vì vậy, trong bất kỳ hàm tạo nào của đối tượng, bạn có thể dựa vào đó RECTANGLEđể được khởi tạo bất hợp pháp sau đó.
Julian Schaub - litb

8
@cirosantilli: Bởi vì từ đầu trong thời gian khởi tạo C ++ là một phần của định nghĩa , không phải là khai báo . Và khai báo thành viên dữ liệu bên trong lớp chỉ là: một khai báo. (Mặt khác, một ngoại lệ được tạo ra cho các thành viên không thể tách rời và enum, và trong C ++ 11 - cho các thành viên const của các loại chữ .)
AnT

153

Trong C ++ 11 bạn có thể làm ngay bây giờ:

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};

30
Thật không may, giải pháp này không hoạt động cho std :: string.
HelloWorld

2
Lưu ý rằng 1. điều này chỉ hoạt động với chữ và 2. đây không phải là tiêu chuẩn phù hợp, mặc dù Gnu / GCC sẽ phạt tiền, các trình biên dịch khác sẽ đưa ra lỗi. Định nghĩa phải có trong cơ thể.
ManuelSchneid3r

2
@ ManuelSchneid3r Chính xác thì điều này "không phù hợp tiêu chuẩn" như thế nào? Nó trông giống như khởi tạo cú đúp chuẩn hoặc không bằng C ++ 11 đối với tôi.
gạch dưới

3
@rvighne, không có gì sai. constexprngụ ý constcho var, không phải cho loại nó điểm. Tức static constexpr const char* constlà giống như static constexpr const char*, nhưng không giống như static constexpr char*.
midenok

2
@ abyss.7 - Cảm ơn câu trả lời của bạn, và tôi có một câu hỏi khác: Tại sao nó phải tĩnh?
Guy Avraham

34

Bên trong định nghĩa lớp bạn chỉ có thể khai báo các thành viên tĩnh. Chúng phải được xác định bên ngoài lớp học. Đối với các hằng số tích phân thời gian biên dịch, tiêu chuẩn tạo ra ngoại lệ mà bạn có thể "khởi tạo" các thành viên. Nó vẫn không phải là một định nghĩa, mặc dù. Lấy địa chỉ sẽ không hoạt động mà không có định nghĩa, ví dụ.

Tôi muốn đề cập rằng tôi không thấy lợi ích của việc sử dụng std :: string over const char [] cho các hằng số . std :: chuỗi là tốt và tất cả nhưng nó đòi hỏi khởi tạo động. Vì vậy, nếu bạn viết một cái gì đó như

const std::string foo = "hello";

ở phạm vi không gian tên, hàm tạo của foo sẽ được chạy ngay trước khi thực hiện bắt đầu chính và hàm tạo này sẽ tạo một bản sao của hằng "xin chào" trong bộ nhớ heap. Trừ khi bạn thực sự cần RECTANGLE để trở thành một chuỗi std :: bạn cũng có thể viết

// class definition with incomplete static member could be in a header file
class A {
    static const char RECTANGLE[];
};

// this needs to be placed in a single translation unit only
const char A::RECTANGLE[] = "rectangle";

Đó! Không phân bổ heap, không sao chép, không khởi tạo động.

Chúc mừng, s.


1
Đây là câu trả lời trước C ++ 11. Sử dụng C ++ tiêu chuẩn và sử dụng std :: string_view.

1
C ++ 11 không có std :: string_view.
Lukas Salich

17

Đây chỉ là thông tin bổ sung, nhưng nếu bạn thực sự muốn chuỗi trong tệp tiêu đề, hãy thử một cái gì đó như:

class foo
{
public:
    static const std::string& RECTANGLE(void)
    {
        static const std::string str = "rectangle";

        return str;
    }
};

Mặc dù tôi nghi ngờ đó là đề nghị.


Trông thật tuyệt :) - tôi đoán bạn có nền tảng về các ngôn ngữ khác ngoài c ++?
lb

5
Tôi sẽ không đề nghị nó. Tôi làm điều này thường xuyên. Nó hoạt động tốt và tôi thấy nó rõ ràng hơn việc đưa chuỗi vào tệp thực hiện. Dữ liệu thực tế của std :: string vẫn nằm trên heap. Tôi sẽ trả về const char *, trong trường hợp đó bạn không cần khai báo biến tĩnh để khai báo sẽ tốn ít dung lượng hơn (mã khôn ngoan). Chỉ là một vấn đề của hương vị mặc dù.
Công cụ phóng to

15

Trong C ++ 17, bạn có thể sử dụng các biến nội tuyến :

class A {
 private:
  static inline const std::string my_string = "some useful string constant";
};

Lưu ý rằng điều này khác với câu trả lời của abyss.7 : Cái này xác định một std::stringđối tượng thực tế , không phải là mộtconst char*


Bạn không nghĩ rằng việc sử dụng inlinesẽ tạo ra nhiều bản sao?
shuva


8

Để sử dụng cú pháp khởi tạo trong lớp đó, hằng số phải là hằng số tĩnh của kiểu tích phân hoặc kiểu liệt kê được khởi tạo bởi một biểu thức hằng.

Đây là hạn chế. Do đó, trong trường hợp này, bạn cần xác định biến ngoài lớp. giới thiệu câu trả lời từ @AndreyT


7

Các biến tĩnh lớp có thể được khai báo trong tiêu đề nhưng phải được định nghĩa trong tệp .cpp. Điều này là do chỉ có thể có một phiên bản của biến tĩnh và trình biên dịch không thể quyết định tệp đối tượng được tạo để đặt nó để bạn phải đưa ra quyết định, thay vào đó.

Để giữ định nghĩa của một giá trị tĩnh với khai báo trong C ++ 11, có thể sử dụng cấu trúc tĩnh lồng nhau. Trong trường hợp này, thành viên tĩnh là một cấu trúc và phải được xác định trong tệp .cpp, nhưng các giá trị nằm trong tiêu đề.

class A
{
private:
  static struct _Shapes {
     const std::string RECTANGLE {"rectangle"};
     const std::string CIRCLE {"circle"};
  } shape;
};

Thay vì khởi tạo các thành viên riêng lẻ, toàn bộ cấu trúc tĩnh được khởi tạo trong .cpp:

A::_Shapes A::shape;

Các giá trị được truy cập với

A::shape.RECTANGLE;

hoặc - vì các thành viên là riêng tư và chỉ được sử dụng từ A - với

shape.RECTANGLE;

Lưu ý rằng giải pháp này vẫn gặp phải vấn đề về thứ tự khởi tạo các biến tĩnh. Khi một giá trị tĩnh được sử dụng để khởi tạo một biến tĩnh khác, đầu tiên có thể chưa được khởi tạo.

// file.h
class File {
public:
  static struct _Extensions {
    const std::string h{ ".h" };
    const std::string hpp{ ".hpp" };
    const std::string c{ ".c" };
    const std::string cpp{ ".cpp" };
  } extension;
};

// file.cpp
File::_Extensions File::extension;

// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };

Trong trường hợp này, các tiêu đề biến tĩnh sẽ chứa {""} hoặc {".h", ".hpp"}, tùy thuộc vào thứ tự khởi tạo được tạo bởi trình liên kết.

Như được đề cập bởi @ abyss.7, bạn cũng có thể sử dụng constexprnếu giá trị của biến có thể được tính toán tại thời điểm biên dịch. Nhưng nếu bạn khai báo các chuỗi của mình static constexpr const char*và chương trình của bạn sử dụng std::stringnếu không sẽ có một chi phí vì một std::stringđối tượng mới sẽ được tạo mỗi khi bạn sử dụng hằng số như vậy:

class A {
public:
   static constexpr const char* STRING = "some value";
};
void foo(const std::string& bar);
int main() {
   foo(A::STRING); // a new std::string is constructed and destroyed.
}

Chuẩn bị tốt câu trả lời Marko. Hai chi tiết: một không cần tệp cpp cho các thành viên lớp tĩnh và cũng vui lòng sử dụng std :: string_view cho bất kỳ loại hằng nào.

4

Tiêu chuẩn hiện tại chỉ cho phép khởi tạo như vậy đối với các loại tích phân không đổi tĩnh. Vì vậy, bạn cần phải làm như AndreyT giải thích. Tuy nhiên, điều đó sẽ có sẵn trong tiêu chuẩn tiếp theo thông qua cú pháp khởi tạo thành viên mới .


4

có thể chỉ cần làm:

static const std::string RECTANGLE() const {
    return "rectangle";
} 

hoặc là

#define RECTANGLE "rectangle"

11
Sử dụng #define khi hằng số gõ có thể được sử dụng là sai.
Artur Czajka

Ví dụ đầu tiên của bạn về cơ bản là một giải pháp tốt nếu bạn không có constexprnhưng bạn không thể tạo một hàm tĩnh const.
Frank Puffer

Giải pháp này nên tránh. Nó tạo ra một chuỗi mới trên mỗi lời gọi. Điều này sẽ tốt hơn:static const std::string RECTANGLE() const { static const std::string value("rectangle"); return value; }
Oz Solomon

Tại sao sử dụng container đầy đủ làm giá trị trả về? Sử dụng std :: string_vew .. nội dung của nó sẽ vẫn hợp lệ trong trường hợp này. thậm chí tốt hơn là sử dụng chuỗi ký tự chuỗi để tạo và trả về chế độ xem chuỗi ... và cuối cùng nhưng không kém phần quan trọng, giá trị trả về const không có ý nghĩa hoặc hiệu ứng nào ở đây ..ah vâng, và điều này là nội tuyến, không phải là tĩnh, trong một số tiêu đề trong không gian tên được đặt tên ... và vui lòng đặt nó thành constexpr

4

Bạn có thể tìm const char*giải pháp được đề cập ở trên, nhưng sau đó nếu bạn cần chuỗi mọi lúc, bạn sẽ có rất nhiều chi phí.
Mặt khác, chuỗi tĩnh cần khởi tạo động, do đó, nếu bạn muốn sử dụng giá trị của nó trong quá trình khởi tạo biến toàn cục / tĩnh khác, bạn có thể gặp phải vấn đề về thứ tự khởi tạo. Để tránh điều đó, điều rẻ nhất là truy cập vào đối tượng chuỗi tĩnh thông qua một getter, kiểm tra xem đối tượng của bạn có được khởi tạo hay không.

//in a header  
class A{  
  static string s;   
public:   
  static string getS();  
};  
//in implementation  
string A::s;  
namespace{  
  bool init_A_s(){  
    A::s = string("foo");   
    return true;  
  }  
  bool A_s_initialized = init_A_s();  
}  
string A::getS(){      
  if (!A_s_initialized)  
    A_s_initialized = init_A_s();  
  return s;  
}  

Nhớ chỉ sử dụng A::getS(). Bởi vì bất kỳ luồng nào chỉ có thể bắt đầu bằng main()A_s_initializedđược khởi tạo trước đó main(), bạn không cần khóa ngay cả trong môi trường đa luồng. A_s_initializedlà 0 theo mặc định (trước khi khởi tạo động), vì vậy nếu bạn sử dụng getS()trước khi s được khởi tạo, bạn gọi hàm init một cách an toàn.

Btw, trong câu trả lời ở trên: " static const std :: string RECTANGLE () const ", các hàm tĩnh không thể constvì chúng không thể thay đổi trạng thái nếu có bất kỳ đối tượng nào (không có con trỏ này).


4

Chuyển nhanh đến năm 2018 và C ++ 17.

  • không sử dụng std :: string, sử dụng std :: string_view bằng chữ
  • xin vui lòng chú ý dưới đây 'constexpr'. Đây cũng là một cơ chế "thời gian biên dịch".
  • không nội tuyến không có nghĩa là lặp lại
  • không có tập tin cpp là không cần thiết cho việc này
  • static_assert 'hoạt động' chỉ trong thời gian biên dịch

    using namespace std::literals;
    
    namespace STANDARD {
    constexpr 
    inline 
    auto 
    compiletime_static_string_view_constant() {
    // make and return string view literal
    // will stay the same for the whole application lifetime
    // will exhibit standard and expected interface
    // will be usable at both
    // runtime and compile time
    // by value semantics implemented for you
        auto when_needed_ =  "compile time"sv;
        return when_needed_  ;
    }

    };

Trên đây là một công dân C ++ chuẩn và hợp pháp. Nó có thể dễ dàng tham gia vào bất kỳ và tất cả các thuật toán std ::, container, tiện ích và như vậy. Ví dụ:

// test the resilience
auto return_by_val = []() {
    auto return_by_val = []() {
        auto return_by_val = []() {
            auto return_by_val = []() {
return STANDARD::compiletime_static_string_view_constant();
            };
            return return_by_val();
        };
        return return_by_val();
    };
    return return_by_val();
};

// actually a run time 
_ASSERTE(return_by_val() == "compile time");

// compile time 
static_assert(
   STANDARD::compiletime_static_string_view_constant() 
   == "compile time" 
 );

Thưởng thức C ++ tiêu chuẩn


Chỉ sử dụng std::string_viewcho hằng số nếu bạn sử dụng string_viewtham số trong tất cả các chức năng của mình. Nếu bất kỳ hàm nào của bạn sử dụng một const std::string&tham số, một bản sao của chuỗi sẽ được tạo khi bạn truyền string_viewhằng số qua tham số đó. Nếu hằng số của bạn thuộc loại, std::stringcác bản sao sẽ không được tạo cho cả const std::string&tham số cũng như std::string_viewtham số.
Marko Mahnič

Câu trả lời hay, nhưng tò mò về lý do tại sao chuỗi_view lại được trả về từ một hàm? Loại mẹo này rất hữu ích trước khi inlinecác biến đến C ++ 17 với ngữ nghĩa ODR của chúng. Nhưng string_view cũng là C ++ 17, do đó, constexpr auto some_str = "compile time"sv;công việc cũng vậy (và thực tế, nó không phải là một biến, nó là constexprnhư vậy inline, nếu bạn có một biến - tức là không constexpr- thì inline auto some_str = "compile time"sv;sẽ làm điều đó, mặc dù tất nhiên là một phạm vi không gian tên biến, về cơ bản là một biến toàn cục, hiếm khi là một ý tưởng tốt).
Mất Mentality
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.