为什么用C ++编码时不建议使用指针?


45

我从某处了解到,在使用C ++时,建议不要使用指针。在使用C ++时,为什么指针这么不好?对于习惯使用指针的C程序员,C ++中更好的替代方法是什么?


40
请链接到“某处”。上下文可能非常相关。

1
希望这个问题对您有用。
Garet Claborn

这些答案大多数都是将避免内存泄漏作为首要原因。我不记得上一次尽管使用指针,但我们的一个应用程序出现了内存泄漏问题。如果存在内存泄漏问题,则说明您使用的工具不正确,或者您不知道自己在做什么。大多数开发环境都有一种自动检查内置泄漏的方法。我认为用垃圾收集的语言来跟踪内存泄漏问题要困难得多,因为它们的发生要细微得多,并且您经常需要使用第三方工具来跟踪罪魁祸首。
Dunk 2014年

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

Answers:


58

我认为它们意味着您应该使用智能指针而不是常规指针。

在计算机科学中,智能指针是一种抽象的数据类型,它在提供附加功能(例如自动垃圾收集或边界检查)的同时模拟指针。这些附加功能旨在减少因滥用指针而导致的错误,同时保持效率。智能指针通常会跟踪它们指向的对象,以进行内存管理。

指针的滥用是错误的主要来源:使用指针编写的程序必须执行的常量分配,释放和引用会带来内存泄漏的风险。智能指针试图通过使资源自动分配来防止内存泄漏:当对象的指针(或一系列指针中的最后一个)被破坏时(例如,由于超出范围,则指向的对象也被破坏)。

在C ++中,重点将放在垃圾回收和防止内存泄漏(仅举两个)上。指针是语言的基本组成部分,因此除了最普通的程序之外,几乎不使用指针是不可能的。


22
通常,它不是严格意义上的经典意义上的垃圾回收,而是更多的引用计数。至少在我习惯使用的智能ptr中,[[boost | std] :: shared_ptr)
Doug T.

3
这个答案非常限于智能指针,这只是问题的一小部分。此外,智能指针不是垃圾回收。
deadalnix 2012年

3
也是一般的RAII。
克莱姆(Klaim)2012年

3
不,这不是什么意思。这只是一个方面,而不是最重要的一个。
康拉德·鲁道夫2012年

我认为智能指针是最重要的方面,但我同意还有很多其他方面。
DeadMG 2012年

97

由于我是发表论战性的“不要使用fcking指针”的人,所以我觉得我应该在这里发表评论。

首先,作为争论,它显然代表了一种极端的观点。有肯定的(原始)指针合法用途。但是我(和许多专业的C ++程序员)坚持认为,这种情况极为罕见。但是,我们真正的意思是:

第一:

原始指针在任何情况下都不得拥有自己的内存。

在这里,“自己的内存”本质上是指在某个时候delete在该指针上调用了它(但是比它更笼统)。该声明可以放心地认为是绝对的。实现自己的智能指针(或其他存储管理策略)时的例外是。即使在那儿,您通常仍应在低级别使用智能指针。

这样做的理由很简单:拥有内存的原始指针会引入错误源。这些错误在现有软件中是多产的:内存泄漏和重复删除-两者都是资源所有权不明确的直接结果(但方向相反)。

只需使用智能指针而不是原始指针,就可以完全免费地彻底解决此问题(注意:当然,这仍然需要思考;共享指针可能导致循环,从而再次导致内存泄漏,但这很容易可以避免)。

第二:

在C ++中,大多数指针的使用都是不必要的。

与其他语言不同,C ++对值语义有很强的支持,并且根本不需要指针的间接调用。并没有立即意识到这一点–历史上,C ++的发明是为了促进C语言中轻松的对象定向,并且在很大程度上依赖于构建通过指针连接的对象图。但是在现代C ++中,这种范例很少是最佳选择,并且现代C ++习惯用法通常根本不需要指针。它们对而不是指针进行操作。

不幸的是,此消息仍未在C ++用户社区中流行。结果,大多数编写的C ++代码仍然充满了多余的指针,这使代码变得复杂,缓慢,错误/不可靠。

对于了解现代C ++的人来说,很明显,您很少需要任何指针(智能指针或原始指针;除非将它们用作迭代器)。生成的代码更短,更简单,更易读,通常更高效,更可靠。


5
igh ...这应该是30多个投票的答案...尤其是第二点。在现代C ++中,指针甚至几乎是没有必要的
查尔斯·萨尔维亚

1
那例如 Gui对象拥有一堆doc对象。它具有这些作为指针,因此可以向前声明该类(避免重新编译),以便可以在创建对象时(使用new)初始化该对象,而不是在某个空状态下在堆栈上创建该对象,然后稍后将其提交?这似乎是指针的一种完全有效的用法-是否所有封装的对象都不应该在堆上?
马丁·贝克特

4
@Martin GUI对象是指针对象图确实是最佳解决方案的一种情况。但是反对拥有内存的原始指针的命令仍然有效。要么在整个过程中使用共享指针,要么开发适当的所有权模型,并且通过原始指针在控件之间仅具有较弱的(=非所有权)关系。
康拉德·鲁道夫

1
@ VF1 std::unique_ptr。另外,为什么不ptr_vec呢?但是通常,带有的值向量仍会交换得更快(尤其是带有移动语义的交换)。
Konrad Rudolph

1
@gaazkam没有人强迫您只使用标准容器。例如,Boost的地图实现支持不完整的类型。另一种解决方案是使用类型擦除。boost::variantrecursive_wrapper可能是我最喜欢的解决方案,代表DAG。
康拉德·鲁道夫

15

仅仅是因为有一些可用的抽象,这些抽象隐藏了使用指针的更多气质,例如访问原始内存和在分配后进行清理。使用智能指针,容器类和RAII之类的设计模式,就可以减少使用原始指针的需要。就是说,就像任何抽象一样,您应该先了解它们的实际工作方式,然后再超越它们。


11

相对而言,C的心态是“遇到问题了?使用指针”。您甚至可以在C ++早期使用成员指针的情况下,在C字符串,函数指针,指针作为迭代器,指针对指针,空指针中看到这一点。

但是在C ++中,您可以将值用于许多或所有这些任务。需要功能抽象吗?std::function。这是一个功能的价值。std::string?这是一个值,那是一个字符串。您可以在C ++上看到类似的方法。这使得对人类和编译器的代码分析变得非常容易。


在C ++中:有问题吗?使用指针。现在您有2个问题...
Daniel Zazula 2015年

10

原因之一是指针的应用范围太广。它们可用于在容器上进行迭代,避免在传递给函数时复制大型对象,非平凡的生命周期管理,访问内存中的随机位置等。一旦将它们用于一个目的,它们的其他功能就会变得可用立即立意独立。

选择特定用途的工具可使代码更简单,意图更清晰-迭代程序的迭代器,生命周期管理的智能指针等。


3

除了已经列出的原因之外,还有一个显而易见的原因:更好的优化。在使用指针算术的前提下,混淆分析过于复杂,而引用则暗示了一个优化器,因此,如果仅使用引用,则可能进行更深入的混淆分析。


2

除了@jmquigley指出的内存泄漏风险外,还可以认为指针和指针算法存在问题,因为指针可以指向内存中的所有位置,从而导致“难以发现错误”和“安全漏洞”。

这就是为什么它们几乎被C#和Java弃用的原因。


期望他们没有在C#中被“弃用”。这个答案很差,拼写令人恐惧,并且陈述不准确。
拉姆猎犬,2012年

1
他们几乎被遗弃了。我很抱歉不听母语。
2012年

1
嘿,我们可以帮忙拼写。您是说说期望还是拒绝?
DeveloperDon

1
在C#中几乎被遗弃了,您仍然可以通过指定unsafe关键字来启用指针
linquize

-1

C ++支持大多数C语言,功能以及对象和类。C已经有了指针和其他东西。

指针是一种非常有用的技术,可以与“对象定向”结合使用,并且C ++支持它们。但是,这种技术难以教导并且难以理解,并且非常容易引起不需要的错误。

许多新的编程语言假装不对对象使用指针,例如Java,.NET,Delphi,Vala,PHP,Scala。但是,仍在“幕后”使用指针。这些“隐藏指针”技术称为“引用”。

无论如何,我将指针视为一种编程模式,是解决某些问题的一种有效方法,而面向对象编程也是如此。

其他开发人员可能有不同的意见。但是,我建议学生和程序员学习如何:

(1)使用无对象的指针

(2)无指针的对象

(3)对象的显式指针

(4)指向对象的“隐藏”指针(AKA 参考);-)

以该顺序。

即使很难教,也很难学习。对象Pascal(Delphi,FreePascal等)和C++(非Java或C#)可用于这些目标。

而且,后来的新手程序员可以转而使用“隐藏的对象指针”编程语言,例如Java,C#,面向对象的PHP等。


19
C ++比起初的“带有类的C”要多得多。
David Thornley

为什么将C ++和C包装在空中引号中?还有“隐藏”,“引用”以及其他所有内容?您是“推销员”而不参与“编程”吗?
phresnel 2014年

我应该将它们加粗。引号既可以用来突出显示,也可以
反过来

-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 ++中需要它时,通常会在代码中遇到设计问题,应予以修复。
2012年

萨尔瓦多,我想您正在尝试讲一些关于VC6的信息,以及它如何具有异常和意外的指针处理功能。该示例可能会受益于注释,并且如果您从编译器中看到警告,则这些警告可能有助于您将答案与问题联系起来。
DeveloperDon

@Mat该示例适用于32位OS和VC6 ++编译器。
萨尔瓦多2012年

6
谈论绝对古老的编译器中的错误代码?不用了,谢谢。
DeadMG 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.