Làm thế nào để khởi tạo các thành viên tĩnh riêng trong C ++?


519

Cách tốt nhất để khởi tạo một thành viên dữ liệu tĩnh, riêng tư trong C ++ là gì? Tôi đã thử điều này trong tệp tiêu đề của mình, nhưng nó mang lại cho tôi các lỗi liên kết lạ:

class foo
{
    private:
        static int i;
};

int foo::i = 0;

Tôi đoán điều này là do tôi không thể khởi tạo một thành viên tư nhân từ bên ngoài lớp học. Vì vậy, cách tốt nhất để làm điều này là gì?


2
Chào Jason. Tôi đã không tìm thấy một nhận xét về khởi tạo mặc định của các thành viên tĩnh (đặc biệt là các thành phần tích phân). Trong thực tế, bạn cần phải viết int foo :: i để trình liên kết có thể tìm thấy nó, nhưng nó sẽ được tự động khởi tạo với 0! Dòng này sẽ là đủ: int foo :: i; (Điều này hợp lệ cho tất cả các đối tượng được lưu trữ trong bộ nhớ tĩnh, trình liên kết chịu trách nhiệm khởi tạo các đối tượng tĩnh.)
Nico

1
Các câu trả lời dưới đây không áp dụng cho một lớp mẫu. Họ nói: việc khởi tạo phải đi vào tệp nguồn. Đối với một lớp mẫu, điều này là không thể, cũng không cần thiết.
Joachim W

7
C ++ 17 cho phép khởi tạo nội tuyến các thành viên dữ liệu tĩnh (ngay cả đối với các loại không nguyên) : inline static int x[] = {1, 2, 3};. Xem en.cppreference.com/w/cpp/lingu/static#Static_data_members
Vladimir Reshetnikov

Câu trả lời:


556

Khai báo lớp phải ở trong tệp tiêu đề (Hoặc trong tệp nguồn nếu không được chia sẻ).
Tập tin: foo.h

class foo
{
    private:
        static int i;
};

Nhưng việc khởi tạo phải ở trong tệp nguồn.
Tập tin: foo.cpp

int foo::i = 0;

Nếu việc khởi tạo nằm trong tệp tiêu đề thì mỗi tệp bao gồm tệp tiêu đề sẽ có định nghĩa về thành viên tĩnh. Do đó, trong giai đoạn liên kết, bạn sẽ gặp lỗi liên kết vì mã để khởi tạo biến sẽ được xác định trong nhiều tệp nguồn. Việc khởi tạo static int iphải được thực hiện bên ngoài bất kỳ chức năng nào.

Lưu ý: Matt Curtis: điểm ra rằng C ++ cho phép đơn giản hóa những điều trên nếu biến thành viên tĩnh là kiểu int const (ví dụ int, bool, char). Sau đó, bạn có thể khai báo và khởi tạo biến thành viên trực tiếp bên trong khai báo lớp trong tệp tiêu đề:

class foo
{
    private:
        static int const i = 42;
};

4
Đúng. Nhưng tôi giả sử câu hỏi đã được đơn giản hóa. Về mặt kỹ thuật, khai báo và định nghĩa đều có thể nằm trong một tệp nguồn duy nhất. Nhưng điều đó sau đó giới hạn việc sử dụng lớp bởi các lớp khác.
Martin York

11
thực ra không chỉ là POD, nó còn phải là một kiểu int nữa (int, short, bool, char ...)
Matt Curtis

9
Lưu ý rằng đây không chỉ là câu hỏi về cách khởi tạo giá trị: các kiểu tích phân const được định nghĩa như thế này có thể được biến thành hằng số thời gian biên dịch khi triển khai. Đây không phải luôn luôn là những gì bạn muốn, vì nó làm tăng sự phụ thuộc nhị phân: mã máy khách cần biên dịch lại nếu giá trị thay đổi.
Steve Jessop

5
@Martin: ngoài việc sửa s / POD / loại tích phân /, nếu địa chỉ đã từng được sử dụng thì cũng cần phải có một định nghĩa. Lạ vì nó có thể nghe, khai báo với bộ khởi tạo, trong định nghĩa lớp, không phải là một định nghĩa. Thành ngữ constplated const cung cấp một cách giải quyết cho các trường hợp bạn cần định nghĩa trong tệp tiêu đề. Một cách giải quyết khác và đơn giản hơn là một hàm tạo ra giá trị của hằng số tĩnh cục bộ. Chúc mừng & hth.,
Chúc mừng và hth. - Alf

3
Bạn có thể thêm một làm rõ rằng int foo :: i = 0; không nên ở bên trong một chức năng (bao gồm cả chức năng chính). Tôi đã có nó khi bắt đầu chức năng chính của tôi và nó không như thế.
qwerty9967

89

Đối với một biến :

foo.h:

class foo
{
private:
    static int i;
};

foo.cpp:

int foo::i = 0;

Điều này là do chỉ có thể có một ví dụ foo::itrong chương trình của bạn. Nó tương đương với extern int itrong một tệp tiêu đề và int itrong một tệp nguồn.

Đối với một hằng số, bạn có thể đặt giá trị thẳng vào khai báo lớp:

class foo
{
private:
    static int i;
    const static int a = 42;
};

2
Đây là một điểm hợp lệ. Tôi sẽ thêm điều này quá lời giải thích của tôi. Nhưng cần lưu ý rằng điều này chỉ hoạt động cho các loại POD.
Martin York

Kể từ khi, C ++ cho phép chỉ tốt với khai báo trong lớp và không có định nghĩa cho các kiểu tích phân. Vì C ++ 98 tự hay C ++ 03 hay khi nào? Xin vui lòng chia sẻ liên kết xác thực xin vui lòng. Từ ngữ chuẩn C ++ không đồng bộ với trình biên dịch. Họ đề cập đến các thành viên sẽ vẫn được xác định nếu họ được sử dụng. Vì vậy, tôi không cần trích dẫn Tiêu chuẩn C ++
smRaj

1
Tôi tự hỏi tại sao privatecác biến có thể được khởi tạo bên ngoài Class ở đây, điều này có thể được thực hiện cho các biến không tĩnh không.
Krishna Oza

Bạn đã tìm thấy lời giải thích? @Krishna_Oza
nn0p

@ nn0p chưa có, nhưng khởi tạo biến riêng tư không tĩnh bên ngoài Classkhông có ý nghĩa gì trong Cpp.
Krishna Oza

41

Kể từ C ++ 17, các thành viên tĩnh có thể được xác định trong tiêu đề với từ khóa nội tuyến .

http://en.cppreference.com/w/cpp/lingu/static

"Một thành viên dữ liệu tĩnh có thể được khai báo nội tuyến. Một thành viên dữ liệu tĩnh nội tuyến có thể được định nghĩa trong định nghĩa lớp và có thể chỉ định một trình khởi tạo thành viên mặc định. Nó không cần một định nghĩa ngoài lớp:"

struct X
{
    inline static int n = 1;
};

1
Điều này là có thể kể từ C ++ 17, hiện đang trong quá trình trở thành tiêu chuẩn mới.
Grebu

31

Đối với những người xem tương lai của câu hỏi này, tôi muốn chỉ ra rằng bạn nên tránh những gì khỉ0506 đang gợi ý .

Các tập tin tiêu đề là để khai báo.

Các tệp tiêu đề được biên dịch một lần cho mỗi .cpptệp trực tiếp hoặc gián tiếp #includeschúng và mã bên ngoài bất kỳ chức năng nào được chạy khi khởi tạo chương trình, trước đó main().

Bằng cách đặt: foo::i = VALUE;vào tiêu đề, foo:isẽ được gán giá trị VALUE(bất kể đó là gì) cho mỗi .cpptệp và các phép gán này sẽ xảy ra theo thứ tự không xác định (được xác định bởi trình liên kết) trước khi main()được chạy.

Điều gì xảy ra nếu chúng ta #define VALUElà một số khác nhau trong một trong các .cpptệp của chúng tôi ? Nó sẽ biên dịch tốt và chúng tôi sẽ không có cách nào để biết ai thắng cho đến khi chúng tôi chạy chương trình.

Không bao giờ đặt mã thực thi vào một tiêu đề cho cùng một lý do mà bạn không bao giờ #includelà một .cpptệp.

bao gồm các trình bảo vệ (mà tôi đồng ý bạn nên luôn luôn sử dụng) bảo vệ bạn khỏi một điều khác biệt: cùng một tiêu đề được gián tiếp #included nhiều lần trong khi biên dịch một .cpptệp duy nhất


2
Tất nhiên, bạn đúng về điều này, ngoại trừ trong trường hợp mẫu lớp (không được hỏi về, nhưng tôi tình cờ phải giải quyết rất nhiều). Vì vậy, nếu lớp được định nghĩa đầy đủ và không phải là mẫu lớp, thì hãy đặt các thành viên tĩnh này vào một tệp CPP riêng, nhưng đối với các mẫu lớp thì định nghĩa phải nằm trong cùng một đơn vị dịch (ví dụ: tệp tiêu đề).
khỉ0506

@ khỉ_05_06: Đó dường như là một đối số để tránh thành viên tĩnh trong mã templated: Bạn đã kết thúc với một thành viên tĩnh cho mỗi lần khởi tạo của lớp. vấn đề trở nên tồi tệ hơn khi có thể biên dịch tiêu đề thành nhiều tệp cpp ... Bạn có thể nhận được một loạt các định nghĩa mâu thuẫn.
Joshua Clayton

publib.boulder.ibm.com/infocenter/macxhelp/v6v81/NH Liên kết này mô tả ngay lập tức các thành viên mẫu tĩnh int hàm chính, sạch hơn, nếu có một chút gánh nặng.
Joshua Clayton

1
Đối số của bạn là thực sự rất lớn. Trước tiên, bạn không thể #define VALUE vì tên macro có ot là một định danh hợp lệ. Và thậm chí nếu bạn có thể - ai sẽ làm điều đó? Các tập tin tiêu đề là để khai báo -? C'mon .. Các trường hợp duy nhất mà bạn nên tránh đặt các giá trị trong tiêu đề là để chống lại việc sử dụng odr. Và đặt giá trị trong tiêu đề có thể dẫn đến việc biên dịch lại không cần thiết bất cứ khi nào bạn cần thay đổi giá trị.
Aleksander Fular

20

Với trình biên dịch Microsoft [1], các biến tĩnh không intgiống như cũng có thể được xác định trong tệp tiêu đề, nhưng bên ngoài khai báo lớp, sử dụng cụ thể của Microsoft __declspec(selectany).

class A
{
    static B b;
}

__declspec(selectany) A::b;

Lưu ý rằng tôi không nói điều này là tốt, tôi chỉ nói nó có thể được thực hiện.

[1] Những ngày này, nhiều trình biên dịch hơn hỗ trợ của MSC __declspec(selectany)- ít nhất là gcc và clang. Có thể nhiều hơn nữa.


17
int foo::i = 0; 

Là cú pháp đúng để khởi tạo biến, nhưng nó phải đi trong tệp nguồn (.cpp) chứ không phải trong tiêu đề.

Bởi vì nó là một biến tĩnh, trình biên dịch chỉ cần tạo một bản sao của nó. Bạn phải có một dòng "int foo: i" một số nơi trong mã của bạn để báo cho trình biên dịch nơi đặt nó nếu không bạn sẽ gặp lỗi liên kết. Nếu đó là trong một tiêu đề, bạn sẽ nhận được một bản sao trong mỗi tệp bao gồm tiêu đề, do đó, nhận được nhiều lỗi biểu tượng được xác định từ trình liên kết.


12

Tôi không có đủ đại diện ở đây để thêm nhận xét này dưới dạng nhận xét, nhưng IMO thật tốt khi viết tiêu đề của bạn bằng #include Guard, điều này được Paranaix lưu ý vài giờ trước sẽ ngăn ngừa lỗi đa định nghĩa. Trừ khi bạn đã sử dụng một tệp CPP riêng biệt, không cần thiết chỉ sử dụng một tệp để khởi tạo các thành viên không tách rời tĩnh.

#ifndef FOO_H
#define FOO_H
#include "bar.h"

class foo
{
private:
    static bar i;
};

bar foo::i = VALUE;
#endif

Tôi thấy không cần sử dụng tệp CPP riêng cho việc này. Chắc chắn, bạn có thể, nhưng không có lý do kỹ thuật tại sao bạn nên phải.


21
#incoide Guard chỉ ngăn chặn nhiều định nghĩa cho mỗi đơn vị dịch thuật.
Paul Fultz II

3
liên quan đến phong cách tốt: bạn nên thêm bình luận trên endif đóng cửa:#endif // FOO_H
Riga

9
Điều này chỉ hoạt động nếu bạn chỉ có một đơn vị biên dịch bao gồm foo.h. Nếu hai hoặc nhiều cpp bao gồm foo.h, đó là một tình huống điển hình, mỗi cpp sẽ khai báo cùng một biến tĩnh để trình liên kết sẽ khiếu nại với nhiều định nghĩa của `foo :: i 'trừ khi bạn sử dụng một trình biên dịch gói với các tệp (biên dịch chỉ có một tệp bao gồm tất cả các cpps). Nhưng mặc dù biên dịch gói là tuyệt vời, giải pháp cho vấn đề là khai báo (int foo :: i = 0;) trong một cpp!
Alejadro Xalabarder

1
Hoặc chỉ sử dụng#pragma once
tambre

12

Nếu bạn muốn khởi tạo một số loại hỗn hợp (chuỗi ký tự), bạn có thể làm một cái gì đó như thế:

class SomeClass {
  static std::list<string> _list;

  public:
    static const std::list<string>& getList() {
      struct Initializer {
         Initializer() {
           // Here you may want to put mutex
           _list.push_back("FIRST");
           _list.push_back("SECOND");
           ....
         }
      }
      static Initializer ListInitializationGuard;
      return _list;
    }
};

ListInitializationGuardlà một biến tĩnh bên trong SomeClass::getList()phương thức, nó sẽ chỉ được xây dựng một lần, điều đó có nghĩa là hàm tạo được gọi một lần. Điều này sẽ initialize _listbiến thành giá trị bạn cần. Bất kỳ cuộc gọi tiếp theo nào getListsẽ chỉ trả về _listđối tượng đã khởi tạo .

Tất nhiên bạn phải truy cập _listđối tượng luôn bằng getList()phương thức gọi .


1
Đây là một phiên bản của thành ngữ này không yêu cầu tạo một phương thức cho mỗi đối tượng thành viên: stackoverflow.com/a/48337288/895245
Ciro Santilli 冠状 病 六四 事件 法轮功

9

Mẫu xây dựng tĩnh C ++ 11 hoạt động cho nhiều đối tượng

Một thành ngữ đã được đề xuất tại: https://stackoverflow.com/a/27088552/895245 nhưng ở đây có phiên bản sạch hơn không yêu cầu tạo phương thức mới cho mỗi thành viên.

main.cpp

#include <cassert>
#include <vector>

// Normally on the .hpp file.
class MyClass {
public:
    static std::vector<int> v, v2;
    static struct StaticConstructor {
        StaticConstructor() {
            v.push_back(1);
            v.push_back(2);
            v2.push_back(3);
            v2.push_back(4);
        }
    } _staticConstructor;
};

// Normally on the .cpp file.
std::vector<int> MyClass::v;
std::vector<int> MyClass::v2;
// Must come after every static member.
MyClass::StaticConstructor MyClass::_staticConstructor;

int main() {
    assert(MyClass::v[0] == 1);
    assert(MyClass::v[1] == 2);
    assert(MyClass::v2[0] == 3);
    assert(MyClass::v2[1] == 4);
}

GitHub ngược dòng .

Biên dịch và chạy:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

Xem thêm: constructor tĩnh trong C ++? Tôi cần khởi tạo các đối tượng tĩnh riêng

Đã thử nghiệm trên Ubuntu 19.04.

Biến nội tuyến C ++ 17

Được đề cập tại: https://stackoverflow.com/a/45062055/895245 nhưng đây là một ví dụ có thể chạy được nhiều biến để làm cho nó rõ ràng hơn: Làm thế nào để các biến nội tuyến hoạt động?


5

Bạn cũng có thể bao gồm bài tập trong tệp tiêu đề nếu bạn sử dụng bộ bảo vệ tiêu đề. Tôi đã sử dụng kỹ thuật này cho một thư viện C ++ mà tôi đã tạo. Một cách khác để đạt được kết quả tương tự là sử dụng các phương thức tĩnh. Ví dụ...

class Foo
   {
   public:
     int GetMyStatic() const
     {
       return *MyStatic();
     }

   private:
     static int* MyStatic()
     {
       static int mStatic = 0;
       return &mStatic;
     }
   }

Đoạn mã trên có "phần thưởng" không yêu cầu tệp CPP / nguồn. Một lần nữa, một phương thức tôi sử dụng cho các thư viện C ++ của mình.


4

Tôi làm theo ý tưởng từ Karl. Tôi thích nó và bây giờ tôi cũng sử dụng nó. Tôi đã thay đổi một chút ký hiệu và thêm một số chức năng

#include <stdio.h>

class Foo
{
   public:

     int   GetMyStaticValue () const {  return MyStatic();  }
     int & GetMyStaticVar ()         {  return MyStatic();  }
     static bool isMyStatic (int & num) {  return & num == & MyStatic(); }

   private:

      static int & MyStatic ()
      {
         static int mStatic = 7;
         return mStatic;
      }
};

int main (int, char **)
{
   Foo obj;

   printf ("mystatic value %d\n", obj.GetMyStaticValue());
   obj.GetMyStaticVar () = 3;
   printf ("mystatic value %d\n", obj.GetMyStaticValue());

   int valMyS = obj.GetMyStaticVar ();
   int & iPtr1 = obj.GetMyStaticVar ();
   int & iPtr2 = valMyS;

   printf ("is my static %d %d\n", Foo::isMyStatic(iPtr1), Foo::isMyStatic(iPtr2));
}

đầu ra này

mystatic value 7
mystatic value 3
is my static 1 0

3

Cũng làm việc trong tập tin privateStatic.cpp:

#include <iostream>

using namespace std;

class A
{
private:
  static int v;
};

int A::v = 10; // possible initializing

int main()
{
A a;
//cout << A::v << endl; // no access because of private scope
return 0;
}

// g++ privateStatic.cpp -o privateStatic && ./privateStatic

3

Một set_default()phương pháp thì sao?

class foo
{
    public:
        static void set_default(int);
    private:
        static int i;
};

void foo::set_default(int x) {
    i = x;
}

Chúng ta sẽ chỉ phải sử dụng set_default(int x)phương thức và staticbiến của chúng ta sẽ được khởi tạo.

Điều này sẽ không bất đồng với các ý kiến ​​còn lại, thực ra nó tuân theo cùng một nguyên tắc khởi tạo biến trong phạm vi toàn cầu, nhưng bằng cách sử dụng phương pháp này, chúng tôi làm cho nó rõ ràng (và dễ hiểu) thay vì có định nghĩa của biến treo ở đó.


3

Vấn đề liên kết bạn gặp phải có thể là do:

  • Cung cấp cả định nghĩa thành viên lớp và tĩnh trong tệp tiêu đề,
  • Bao gồm tiêu đề này trong hai hoặc nhiều tệp nguồn.

Đây là một vấn đề phổ biến cho những người bắt đầu với C ++. Thành viên lớp tĩnh phải được khởi tạo trong một đơn vị dịch nghĩa là trong tệp nguồn đơn.

Thật không may, thành viên lớp tĩnh phải được khởi tạo bên ngoài thân lớp. Điều này làm phức tạp việc viết mã chỉ tiêu đề, và do đó, tôi đang sử dụng cách tiếp cận khá khác nhau. Bạn có thể cung cấp đối tượng tĩnh của mình thông qua hàm lớp tĩnh hoặc không tĩnh, ví dụ:

class Foo
{
    // int& getObjectInstance() const {
    static int& getObjectInstance() {
        static int object;
        return object;
    }

    void func() {
        int &object = getValueInstance();
        object += 5;
    }
};

1
Tôi vẫn là một n00b hoàn chỉnh cho đến khi C ++ đi, nhưng điều này có vẻ tuyệt vời với tôi, cảm ơn bạn rất nhiều! Tôi được quản lý vòng đời hoàn hảo của đối tượng singleton miễn phí.
Rafael Kitover

2

Một cách "trường học cũ" để xác định các hằng số là thay thế chúng bằng enum:

class foo
{
    private:
        enum {i = 0}; // default type = int
        enum: int64_t {HUGE = 1000000000000}; // may specify another type
};

Cách này không yêu cầu cung cấp định nghĩa và tránh tạo ra giá trị không đổi , điều này có thể giúp bạn đỡ đau đầu, ví dụ như khi bạn vô tình sử dụng ODR .


1

Tôi chỉ muốn đề cập đến một cái gì đó hơi lạ với tôi khi lần đầu tiên tôi gặp phải điều này.

Tôi cần khởi tạo một thành viên dữ liệu tĩnh riêng trong một lớp mẫu.

trong .h hoặc .hpp, có vẻ như thế này để khởi tạo một thành viên dữ liệu tĩnh của một lớp mẫu:

template<typename T>
Type ClassName<T>::dataMemberName = initialValue;

0

Điều này phục vụ mục đích của bạn?

//header file

struct MyStruct {
public:
    const std::unordered_map<std::string, uint32_t> str_to_int{
        { "a", 1 },
        { "b", 2 },
        ...
        { "z", 26 }
    };
    const std::unordered_map<int , std::string> int_to_str{
        { 1, "a" },
        { 2, "b" },
        ...
        { 26, "z" }
    };
    std::string some_string = "justanotherstring";  
    uint32_t some_int = 42;

    static MyStruct & Singleton() {
        static MyStruct instance;
        return instance;
    }
private:
    MyStruct() {};
};

//Usage in cpp file
int main(){
    std::cout<<MyStruct::Singleton().some_string<<std::endl;
    std::cout<<MyStruct::Singleton().some_int<<std::endl;
    return 0;
}
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.