Почему указатели не рекомендуются при кодировании на C ++?

45

Я где-то читал, что при использовании C ++ рекомендуется не использовать указатели. Почему указатели такие плохие идеи, когда вы используете C ++. Для программистов C, которые привыкли использовать указатели, что является лучшей альтернативой и подходом в C ++?

Джошуа Партоги
источник
40
пожалуйста, ссылку на "где-то". Контекст может быть очень актуальным.
1
Этот вопрос , надеюсь, будет полезен для вас.
Гарет Клаборн
Большинство из этих ответов относятся к предотвращению утечек памяти в качестве основной причины. Я не могу вспомнить, когда в прошлом у одного из наших приложений была проблема утечки памяти, несмотря на использование указателей. Если у вас есть проблемы с утечкой памяти, значит, вы не используете правильные инструменты или не знаете, что делаете. В большинстве сред разработки есть встроенный способ автоматической проверки на наличие утечек. Я думаю, что проблемы утечек памяти гораздо сложнее отследить в языках с сборкой мусора, потому что их возникновение гораздо более тонкое, и вам часто требуется сторонний инструмент для обнаружения виновника. ,
Данк
1
Добавляя к комментарию @Dunk, иногда встроенные сборщики мусора в языках более высокого уровня просто не работают правильно. Например, сборщик мусора в ActionScript 3 этого не делает. Сейчас в ней есть ошибка, связанная с NetConnectionотключением экземпляров от сервера ( stackoverflow.com/questions/14780456/… ), а также проблема с наличием в программе нескольких объектов, которые она специально откажется собирать. ...
Panzercrisis
... ( adobe.com/devnet/actionscript/learning/as3-fundamentals/… - поиск GCRoots are never garbage collected.и начатый абзац The MMgc is considered a conservative collector for mark/sweep.). Технически это проблема в Adobe Virtual Machine 2, а не в самой AS3, но когда у вас возникают подобные проблемы на языках более высокого уровня, в которые встроена сборка мусора, у вас часто нет никакого истинного способа отладки языка. эти проблемы полностью выходят за рамки программы. ...
Panzercrisis

Ответы:

58

Я думаю, что они означают, что вы должны использовать умные указатели вместо обычных указателей.

В компьютерных науках интеллектуальный указатель - это абстрактный тип данных, который имитирует указатель, предоставляя при этом дополнительные функции, такие как автоматический сбор мусора или проверка границ. Эти дополнительные функции предназначены для уменьшения ошибок, вызванных неправильным использованием указателей при сохранении эффективности. Интеллектуальные указатели обычно отслеживают объекты, на которые они указывают, с целью управления памятью.

Неправильное использование указателей является основным источником ошибок: постоянное выделение, освобождение и ссылки, которые должны выполняться программой, написанной с использованием указателей, создает риск возникновения утечек памяти. Интеллектуальные указатели пытаются предотвратить утечки памяти, делая автоматическое освобождение ресурсов: когда указатель (или последний в ряду указателей) на объект уничтожается, например, из-за того, что он выходит из области видимости, указанный объект также уничтожается.

В C ++ акцент будет делаться на сборку мусора и предотвращение утечек памяти (просто назвать два). Указатели являются фундаментальной частью языка, поэтому их использование практически невозможно, кроме как в большинстве программ.

jmq
источник
22
как правило, это не строго сборка мусора в классическом смысле, больше подсчета ссылок. По крайней мере, в умном ptr, к которому я привык ([boost | std] :: shared_ptr)
Дуг Т.
3
Этот ответ очень ограничен умным указателем, который является лишь небольшим аспектом проблемы. Кроме того, смарт-указатель не сборщик мусора.
Deadalnix
3
Также RAII в целом.
Klaim
3
Нет, это не то, что имеется в виду. Это только один аспект, и не самый важный.
Конрад Рудольф
Я думаю, что умные указатели являются наиболее важным аспектом, но я согласен, что есть много других.
DeadMG
97

Поскольку я - тот, кто опубликовал полемику «не используйте чертовы указатели», я чувствую, что должен прокомментировать здесь.

Прежде всего, как полемика, это, очевидно, крайняя точка зрения. Там является , безусловно , законным использованием (сырья) указателей. Но я (и многие профессиональные программисты на С ++) утверждаю, что эти случаи чрезвычайно редки. Но на самом деле мы имеем в виду следующее:

Первый:

Сырые указатели ни в коем случае не должны иметь собственную память.

Здесь «собственная память», по сути, означает, что в какой-то момент deleteвызывается этот указатель (но он более общий, чем этот). Это утверждение можно смело считать абсолютным. Только исключение при реализации собственного интеллектуального указателя (или стратегии управления другой памяти). И даже там , вы должны нормально все еще использовать смарт - указатель на низком уровне.

Обоснование этого довольно простое: необработанные указатели, собственная память которых вводит источник ошибки. И эти ошибки распространены в существующем программном обеспечении: утечки памяти и двойное удаление - оба являются прямым следствием неясного владения ресурсами (но в противоположном направлении).

Эта проблема может быть полностью устранена, практически бесплатно, просто используя умные указатели вместо необработанных указателей (предостережение: это, конечно, требует размышлений; общие указатели могут привести к циклам и, таким образом, к утечкам памяти - но это легко избежать).

Во-вторых:

В большинстве случаев использование указателей в C ++ не нужно.

В отличие от других языков, C ++ имеет очень сильную поддержку семантики значений и просто не нуждается в косвенных указателях. Это не было немедленно понято - исторически, C ++ был изобретен, чтобы облегчить легкую ориентацию объекта в C, и в значительной степени полагался на построение графов объектов, которые были связаны указателями. Но в современном C ++ эта парадигма редко является лучшим выбором, и современные идиомы C ++ часто вообще не нуждаются в указателях . Они оперируют ценностями, а не указателями.

К сожалению, это сообщение до сих пор не завоевало популярность в сообществе пользователей C ++. В результате большая часть написанного кода на C ++ все еще усеяна лишними указателями, которые делают код сложным, медленным и неисправным / ненадежным.

Для тех, кто знает современный C ++, ясно, что вам очень редко нужны какие-либо указатели (умные или необработанные; кроме случаев использования их в качестве итераторов). Получающийся код короче, менее сложен, более читабелен, часто более эффективен и более надежен.

Конрад Рудольф
источник
5
Вздох ... это действительно должен быть ответ с более чем 30 голосами "за" ... особенно по второму пункту. Указатели просто редко нужны в современном C ++.
Чарльз Сальвия
1
как насчет, например. Объект Gui владеет кучей объектов документации. Они имеют оба указателя, так что класс может быть объявлен вперед (избегает перекомпиляции) и чтобы объект мог инициализироваться при создании (с новым), а не создаваться в стеке в каком-то пустом состоянии, а затем вноситься в файл позже? Кажется, это вполне допустимое использование указателей - не должны ли все инкапсулированные объекты быть в куче?
Мартин Беккет,
4
@Martin GUI объекты являются одним из случаев, когда графы указателей объектов действительно являются лучшим решением. Но указ против владеющих памятью необработанных указателей остается в силе. Либо используйте общие указатели повсюду, либо разработайте правильную модель владения и имейте только слабые (= не владеющие) отношения между элементами управления через необработанные указатели.
Конрад Рудольф
1
@ VF1 std::unique_ptr. Кроме того, почему нет ptr_vec? Но обычно вектор значений с по-прежнему будет меняться быстрее (особенно с семантикой перемещения).
Конрад Рудольф
1
@gaazkam Никто не заставлял вас использовать только стандартные контейнеры. Boost, например, имеет реализацию карты с поддержкой неполных типов. Другое решение заключается в использовании стирания типа; boost::variantс, recursive_wrapperвероятно, мое любимое решение для представления DAG.
Конрад Рудольф
15

Просто потому, что вам доступны абстракции, которые скрывают более темпераментные аспекты использования указателей, такие как доступ к необработанной памяти и очистка после ваших выделений. Благодаря умным указателям, классам контейнеров и шаблонам проектирования, таким как RAII, необходимость в использовании необработанных указателей уменьшается. Тем не менее, как и любая абстракция, вы должны понять, как они на самом деле работают, прежде чем выйти за их пределы.

Эд С.
источник
11

Относительно просто менталитет Си - «Есть проблема? Использовать указатель». Это можно увидеть в строках C, указателях функций, указателях-итераторах, указателях-указателях, пустых указателях - даже в первые дни C ++ с указателями на члены.

Но в C ++ вы можете использовать значения для многих или всех этих задач. Нужна функция абстракция? std::function, Это значение, которое является функцией. std::string? Это значение, это строка. Вы можете увидеть похожие подходы во всем C ++. Это значительно упрощает анализ кода как для людей, так и для компиляторов.

DeadMG
источник
В C ++: есть проблема? Используйте указатель. Теперь у вас есть 2 проблемы ...
Даниэль Зазула
10

Одна из причин - слишком широкое применение указателей. Их можно использовать для итерации по контейнерам, чтобы избежать копирования больших объектов при передаче в функцию, нетривиального управления продолжительностью жизни, доступа к произвольным местам в памяти и т. Д. И как только вы используете их для одной цели, становятся доступны другие их функции немедленно независимо от намерения.

Выбор инструмента для конкретной цели делает код более простым и более наглядным - итераторы для итераций, интеллектуальные указатели для управления временем жизни и т. Д.

maxim1000
источник
3

Помимо перечисленных причин, есть и очевидная причина: лучшая оптимизация. Анализ алиасов слишком сложен из-за арифметики указателей, тогда как ссылки намекают на оптимизатор, поэтому более глубокий анализ алиасов возможен, если используются только ссылки.

SK-логика
источник
2

Помимо риска утечек памяти, указанных в @jmquigley, указатель и арифметика указателя могут считаться проблематичными, поскольку указатели могут указывать повсюду в памяти, вызывая «трудности с обнаружением ошибок» и «уязвимости безопасности».

Вот почему они были почти заброшены в C # и Java.

k3b
источник
Ожидайте, что они не были «заброшены» в C #. Этот ответ плохой, имеет ужасное правописание и ужасное неточное утверждение.
Ramhound
1
Они были почти заброшены. Я прошу прощения за то, что не был носителем языка.
k3b
1
Эй, мы можем помочь с написанием. Вы хотели сказать, ожидать или кроме?
DeveloperDon
1
Практически заброшенный в C #, вы все равно можете включить указатель, указав unsafeключевое слово
linquize
-1

C ++ поддерживает большинство функций C , а также объекты и классы. С уже были указатели и прочее.

Указатели являются очень полезной техникой, которую можно комбинировать с объектной ориентацией, и C ++ поддерживает их. Но этот метод сложен в обучении и труден для понимания, и его очень легко вызвать нежелательные ошибки.

Многие новые языки программирования делают вид, что не используют указатели на объекты, такие как Java, .NET, Delphi, Vala, PHP, Scala. Но указатели по-прежнему используются «за кадром». Эти методы «скрытого указателя» называются «ссылками».

В любом случае, я рассматриваю указатель (и) как шаблон программирования, как правильный способ решения определенных проблем, как это делает объектно-ориентированное программирование .

Другие разработчики могут иметь другое мнение. Но я предлагаю студентам и программистам научиться:

(1) Используйте указатели без объектов

(2) объекты без указателей

(3) явные указатели на объекты

(4) «скрытые» указатели на объекты ( ссылка AKA ) ;-)

В этой последовательности.

Даже если это трудно учить, и трудно учиться. Объект Pascal (Delphi, FreePascal, другие) и C++(не Java или C #) могут быть использованы для этих целей.

И позже начинающие программисты могут переходить на языки программирования «скрытые указатели на объекты», такие как: Java, C #, объектно-ориентированный PHP и другие.

umlcat
источник
19
C ++ намного больше, чем «C with Classes», с которого он начинал.
Дэвид Торнли
Почему вы заключаете C ++ и C в воздушные кавычки? А "скрытые", "ссылки" и все остальное? Вы «продавец» и не участвуете в «программировании»?
Френел
Я должен выделить их жирным шрифтом. Цитаты могут быть использованы, чтобы выделить, но и наоборот
umlcat
-6

Говоря о VC6, когда вы приводите указатель класса (который вы создаете) в переменную (например, DWORD), даже если этот указатель является локальным, вы можете получить доступ к классу через все функции, которые используют одну и ту же кучу. Созданный экземпляр класса определяется как локальный, но на самом деле это не так. Насколько я знаю, любой адрес переменной кучи, структуры или класса уникален на протяжении всей жизни класса хостинга.

Пример:

class MyClass1 {
    public:
        void A (void);
        void B (void);
        void C (void);
    private:
        DWORD dwclass;
};

class MyClass2 {
    public:
        int C (int i);
};

void MyClass1::A (void) {
    MyClass2 *myclass= new MyClass2;
    dwclass=(DWORD)myclass;
}

void MyClass1::B (void) {
    MyClass2 *myclass= (MyClass2 *)dwclass;
    int i = myclass->C(0); // or int i=((MyClass2 *)dwclass)->C(0);
}

void MyClass1::B (void) {
    MyClass2 *myclass= (MyClass2 *)dwclass;
    delete myclass;
}

РЕДАКТИРОВАТЬ Это очень маленькая часть исходного кода. Класс CSRecodset является только классом приведения в CXdbRecordset, где весь реальный код. Поступая так, я могу позволить пользователю воспользоваться тем, что я написал, не теряя своих прав. Я не претендую на то, чтобы продемонстрировать, что мой движок баз данных работает профессионально, но он действительно работает.

//-------------------------------------
class CSRecordSet : public CSObject
//-------------------------------------
{
public:
    CSRecordSet();
    virtual ~CSRecordSet();
    // Constructor
    bool Create(CSDataBase* pDataBase,CSQueryDef* pQueryDef);
    //Open, find, close
    int OpenRst(bool bReadBlanks=0,bool bCheckLastSql=0,bool bForceLoad=0,bool bMessage=1);     // for a given SQL
    int FindRecord(bool bNext);         // for a given SQL
    // TABLE must be ordered by the same fields that will be seek
    bool SeekRecord(int nFieldIndex, char *key, int length=0);  // CRT bsearch
    bool SeekRecord(int nFieldIndex, long key);     
    bool SeekRecord(int nFieldIndex, double key, int decimals);     
    bool SeekRecord(XSEK *SEK);     
    bool DeleteRecord(void);
    bool Close(void);
    // Record Position:
    bool IsEOF(void);           // pointer out of bound
    bool IsLAST(void);          // TRUE if last record
    bool IsBOF(void);           // pointer out of bound
    bool IsOpen(void);
    bool Move(long lRows);      // returns FALSE if out of bound
    void MoveNextNotEof(void);  // eof is tested
    void MoveNext(void);        // eof is not tested
    void MovePrev(void);        // bof is tested
    void MoveLast(void);
    void MoveFirst(void);
    void SetAbsolutePosition(long lRows);
    long GetAbsolutePosition(void);
    void GoToLast(void); // Restore position after a Filter
    // Table info
    long GetRecordCount(void);
    int GetRstTableNumber(void);
    int GetRecordLength(void); //includes stamp (sizeof char)
    int GetTableType(void);
    // Field info
    int GetFieldCount(void);
    void GetFieldName(int nFieldIndex, char *pbuffer);
    int GetFieldIndex(const char *sFieldName);
    int GetFieldSize(int nFieldIndex);
    int GetFieldDGSize(int nFieldIndex); // String size (i.e. dg_Boolean)
    long GetRecordID(void);
    int GetStandardFieldCount(void);
    bool IsMemoFileTable(void);
    bool IsNumberField(int nFieldIndex);
    int GetFieldType(int nFieldIndex);
    // Read Field value
    bool GetFieldValue(int nFieldIndex, XdbVar& var);
    bool GetFieldValueIntoBuffer(int nFieldIndex,char *pbuffer);
    char *GetMemoField(int nMemoFieldIndex, char *pbuffer, int buf_size);
    bool GetBinaryField(unsigned char *buffer,long *buf_size);
    // Write Field value
    void Edit(void); // required
    bool SetFieldValue(int nFieldIndex, XdbVar& var);
    bool SetFieldValueFromBuffer(int nFieldIndex,const char *pbuffer);
    bool Update(void); // required
    // pointer to the same lpSql
    LPXSQL GetSQL(void);
};

//---------------------------------------------------
CSRecordSet::CSRecordSet(){
//---------------------------------------------------
    pClass |= (CS_bAttach);
}
CSRecordSet::~CSRecordSet(){
    if(pObject) delete (CXdbRecordset*)pObject;
}
bool CSRecordSet::Create(CSDataBase* pDataBase,CSQueryDef* pQueryDef){
    CXdbQueryDef *qr=(CXdbQueryDef*)pQueryDef->GetObject();
    CXdbTables *db=(CXdbTables*)pDataBase->GetObject();
    CXdbRecordset *rst = new CXdbRecordset(db,qr);
    if(rst==NULL) return 0;
    pObject = (unsigned long) rst;
    return 1;
}
bool CSRecordSet::Close(void){
    return ((CXdbRecordset*)pObject)->Close();
}
int CSRecordSet::OpenRst(bool bReadBlanks,bool bCheckLastSql,bool bForceLoad, bool bMessage){
    unsigned long dw=0L;
    if(bReadBlanks) dw|=SQL_bReadBlanks;
    if(bCheckLastSql) dw|=SQL_bCheckLastSql;
    if(bMessage) dw|=SQL_bRstMessage;
    if(bForceLoad) dw|=SQL_bForceLoad;

    return ((CXdbRecordset*)pObject)->OpenEx(dw);
}
int CSRecordSet::FindRecord(bool bNext){
    return ((CXdbRecordset*)pObject)->FindRecordEx(bNext);
}
bool CSRecordSet::DeleteRecord(void){
    return ((CXdbRecordset*)pObject)->DeleteEx();
}
bool CSRecordSet::IsEOF(void){
    return ((CXdbRecordset*)pObject)->IsEOF();
}
bool CSRecordSet::IsLAST(void){
    return ((CXdbRecordset*)pObject)->IsLAST();
}
bool CSRecordSet::IsBOF(void){
    return ((CXdbRecordset*)pObject)->IsBOF();
}
bool CSRecordSet::IsOpen(void){
    return ((CXdbRecordset*)pObject)->IsOpen();
}
bool CSRecordSet::Move(long lRows){
    return ((CXdbRecordset*)pObject)->MoveEx(lRows);
}
void CSRecordSet::MoveNextNotEof(void){
    ((CXdbRecordset*)pObject)->MoveNextNotEof();
}
void CSRecordSet::MoveNext(void){
    ((CXdbRecordset*)pObject)->MoveNext();
}
void CSRecordSet::MovePrev(void){
    ((CXdbRecordset*)pObject)->MovePrev();
}
void CSRecordSet::MoveLast(void){
    ((CXdbRecordset*)pObject)->MoveLast();
}
void CSRecordSet::MoveFirst(void){
    ((CXdbRecordset*)pObject)->MoveFirst();
}
void CSRecordSet::SetAbsolutePosition(long lRows){
    ((CXdbRecordset*)pObject)->SetAbsolutePosition(lRows);
}
long CSRecordSet::GetAbsolutePosition(void){
    return ((CXdbRecordset*)pObject)->m_AbsolutePosition;
}
long CSRecordSet::GetRecordCount(void){
    return ((CXdbRecordset*)pObject)->GetRecordCount();
}
int CSRecordSet::GetFieldCount(void){
    return ((CXdbRecordset*)pObject)->GetFieldCount();
}
int CSRecordSet::GetRstTableNumber(void){
    return ((CXdbRecordset*)pObject)->GetRstTableNumber();
}
void CSRecordSet::GetFieldName(int nFieldIndex, char *pbuffer){
    ((CXdbRecordset*)pObject)->GetFieldName(nFieldIndex,pbuffer);
}
int CSRecordSet::GetFieldIndex(const char *sFieldName){
    return ((CXdbRecordset*)pObject)->GetFieldIndex(sFieldName);
}
bool CSRecordSet::IsMemoFileTable(void){
    return ((CXdbRecordset*)pObject)->IsMemoFileTable();
}
bool CSRecordSet::IsNumberField(int nFieldIndex){
    return ((CXdbRecordset*)pObject)->IsNumberField(nFieldIndex);
}
bool CSRecordSet::GetFieldValueIntoBuffer(int nFieldIndex,char *pbuffer){
    return ((CXdbRecordset*)pObject)->GetFieldValueIntoBuffer(nFieldIndex,pbuffer);
}
void CSRecordSet::Edit(void){
    ((CXdbRecordset*)pObject)->Edit();
}
bool CSRecordSet::Update(void){
    return ((CXdbRecordset*)pObject)->Update();
}
bool CSRecordSet::SetFieldValue(int nFieldIndex, XdbVar& var){
    return ((CXdbRecordset*)pObject)->SetFieldValue(nFieldIndex,var);
}
bool CSRecordSet::SetFieldValueFromBuffer(int nFieldIndex,const char *pbuffer){
    return ((CXdbRecordset*)pObject)->SetFieldValueFromBuffer(nFieldIndex,pbuffer);
}
bool CSRecordSet::GetFieldValue(int nFieldIndex, XdbVar& var){
    return ((CXdbRecordset*)pObject)->GetFieldValue(nFieldIndex,var);
}
bool CSRecordSet::SeekRecord(XSEK *SEK){
    return ((CXdbRecordset*)pObject)->TableSeek(SEK);
}
bool CSRecordSet::SeekRecord(int nFieldIndex,char *key, int length){
    return ((CXdbRecordset*)pObject)->TableSeek(nFieldIndex,key,length);
}
bool CSRecordSet::SeekRecord(int nFieldIndex,long i){
    return ((CXdbRecordset*)pObject)->TableSeek(nFieldIndex,i);
}
bool CSRecordSet::SeekRecord(int nFieldIndex, double d, int decimals)
{
    return ((CXdbRecordset*)pObject)->TableSeek(nFieldIndex,d,decimals);
}
int CSRecordSet::GetRecordLength(void){
    return ((CXdbRecordset*)pObject)->GetRecordLength();
}
char *CSRecordSet::GetMemoField(int nMemoFieldIndex,char *pbuffer, int BUFFER_SIZE){
    return ((CXdbRecordset*)pObject)->GetMemoField(nMemoFieldIndex,pbuffer,BUFFER_SIZE);
}
bool CSRecordSet::GetBinaryField(unsigned char *buffer,long *buf_size){
    return ((CXdbRecordset*)pObject)->GetBinaryField(buffer,buf_size);
}
LPXSQL CSRecordSet::GetSQL(void){
    return ((CXdbRecordset*)pObject)->GetSQL();
}
void CSRecordSet::GoToLast(void){
    ((CXdbRecordset*)pObject)->GoToLast();
}
long CSRecordSet::GetRecordID(void){
    return ((CXdbRecordset*)pObject)->GetRecordID();
}
int CSRecordSet::GetStandardFieldCount(void){
    return ((CXdbRecordset*)pObject)->GetStandardFieldCount();
}
int CSRecordSet::GetTableType(void){
    return ((CXdbRecordset*)pObject)->GetTableType();
}
int CSRecordSet::GetFieldType(int nFieldIndex){
    return ((CXdbRecordset*)pObject)->GetFieldType(nFieldIndex);
}
int CSRecordSet::GetFieldDGSize(int nFieldIndex){
    return ((CXdbRecordset*)pObject)->GetFieldDGSize(nFieldIndex);
}
int CSRecordSet::GetFieldSize(int nFieldIndex){
    return ((CXdbRecordset*)pObject)->GetFieldSize(nFieldIndex);
}

РЕДАКТИРОВАТЬ: запрошено DeadMG:

void nimportequoidumomentquecaroule(void) {

    short i = -4;
    unsigned short j=(unsigned short)i;

}
Сальвадор
источник
1
Это описание может быть значительно улучшено с помощью некоторого кода для иллюстрации того, что вы описываете. У меня есть ощущение, что это связано с первоначальным вопросом, но если вы предупредите нас об опасности в этом сценарии, это поможет разработать тему спрашивающего.
DeveloperDon
1
Это приведение DWORDявляется оскорбительным и, возможно, неправильным (DWORD не обязательно достаточно широк, чтобы содержать указатель). Если вам нужен нетипизированный указатель, используйте void*- но когда вам это нужно в C ++, у вас часто возникает проблема с дизайном в вашем коде, которую вы должны исправить.
Мат
Сальвадор, я думаю, вы пытаетесь что-то сказать о VC6 и о его необычной и неожиданной обработке указателей. В этом примере могут быть полезны комментарии, и если вы видите предупреждения от компилятора, они могут быть информативными в отношении связи вашего ответа на вопрос.
DeveloperDon
@Mat Это пример для 32-битной ОС и компилятора VC6 ++.
Сальвадор
6
Говорить о плохом коде в абсолютно древнем компиляторе? Спасибо, не надо.
DeadMG