首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C++ 11 -修改结构向量中的结构成员

C++ 11 -修改结构向量中的结构成员
EN

Stack Overflow用户
提问于 2017-08-26 19:15:21
回答 1查看 404关注 0票数 1

我在其他地方和这里寻找过这样做的方法,但没有找到任何解决我的问题和担忧的答案。

约束:

  1. 我正在使用C++ 11嵌入式设备上。
  2. 我不能用std::string
  3. 我不能使用std::make_unique() (但我可以在new中使用std::unique_ptr )
  4. 我不能用strcpy_s()

问题我有

我遇到的主要问题是,在AvailableZones::upsertZone方法中,如果向量不存在,我想向它添加一个区域(使用名称参数作为“键”)。如果存在,我想更新该区域的温湿度成员。“添加”部件工作,但是更新部分不起作用。

我的下一个问题是AvailableZones::findZone成员。我希望能够返回一个区域,以便调用方不负责释放/删除返回的值。

关注点:

作为C++的新手,我很确定我并没有用正确的C++11方式做很多事情。我对任何/所有的指导都很开放(实际上)。在AvailableZones::findZone方法中,我希望返回我拥有的Zone,而不是创建一个副本或使用new/malloc。看起来我需要使用常规的for/while循环吗?我见过一些迭代器代码,但是它看起来很混乱/复杂,但我不确定使用迭代器是否也能解决这个问题。

最佳实践相关问题:

  1. Zone结构的析构函数中,如果使用delete,则在运行代码时会导致和异常。很明显我做错了什么。
  2. Zone结构中,我可以使name成员成为std::unique_ptr吗?如果是,怎么做?我尝试过很多种方法,但我既不能让它编译,也不能工作。
  3. 是否有更好的方法来实现Zone构造函数?

代码

我在代码中添加了注释,以解释该方法的意图以及我需要帮助的地方。

代码语言:javascript
复制
#include "stdafx.h"
#include <iostream>
#include <assert.h>
#include <memory>
#include <vector>

using namespace std;

struct Zone {
    Zone() {}
    Zone(const char* name, const float temperature, const float humidity)
    {
        auto bufferSize = snprintf(NULL, 0, "%s", name);
        this->name = new char[bufferSize + 1];
        strcpy(this->name, name);
        this->temperature = temperature;
        this->humidity = humidity;
    }

    ~Zone() {
        // deleting name here causes an Exception
        //delete [] name;
    }

    char* name = nullptr;
    float temperature = 0.0f;
    float humidity = 0.0f;
};

class AvailableZones {
public:
    AvailableZones::AvailableZones() {
        m_zoneVec = std::vector<Zone>();
    }

    ~AvailableZones() {
    }

    /*
        Using Arguments, add a Zone to the private zoneVec member is it does not exist
        If is does exist (names of zones are unique and used as the "key"), then update
        the temperature and humidity of the existing zone with those in the arguments
    */
    void AvailableZones::upsertZone(const char *name, const float temperature, const float humidity) {

        for (auto zone : m_zoneVec) {
            if (strcmp(zone.name, name) == 0) {
                zone.temperature = temperature;
                zone.humidity = humidity;
                return;
            }
        }

        m_zoneVec.push_back(Zone(name, temperature, humidity));
    }

    /*
        Given a Zone name, find the zone and return it
        If a Zone with the given name does not exist
        return a nullptr
    */
    const Zone *AvailableZones::findZone(const char *name) const {

        for (auto zone : m_zoneVec) {
            if (strcmp(zone.name, name) == 0) {
                // I know this is not correct.
                // How would I do this, without using "new" and thus
                // forcing the caller to be responsible for deleting?
                return &zone;
            }
        }

        return nullptr;
    }

private:
    std::vector<Zone> m_zoneVec;
};


int main()
{
    auto livingRoom = "Living Room";
    AvailableZones availableZones;
    availableZones.upsertZone("Master Bedroom", 72.0f, 50.0f);
    availableZones.upsertZone(livingRoom, 70.0f, 48.0f);
    availableZones.upsertZone("Study", 68.0f, 46.0f);

    auto foundZone = availableZones.findZone(livingRoom);
    cout << foundZone->name << endl;
    cout << foundZone->temperature << endl;
    cout << foundZone->humidity << endl;

    assert(strcmp(livingRoom, foundZone->name) == 0);
    assert(70.0f == foundZone->temperature);
    assert(48.0f == foundZone->humidity);

    availableZones.upsertZone(livingRoom, 74.0f, 52.0f);

    foundZone = availableZones.findZone(livingRoom);

    assert(strcmp(livingRoom, foundZone->name) == 0);
    assert(74.0f == foundZone->temperature);
    assert(52.0f == foundZone->humidity);

    return 0;
}

编辑:下面的代码实现了@max66以及@ Vaughn Cato和@Artemy Vysotsky提出的建议。这段代码现在按照我的要求工作。所作的修改如下:

  1. 基于范围的循环使用引用(或const引用(视情况而定)。默认情况下,基于范围的循环按值提供元素( @Vaughn Cato也建议这样做)
  2. upsertZone方法中,我使用emplace_back(),以便在容器提供的位置就地创建区域实例。使用push_back() (早期的代码)创建了一个临时副本,结果被丢弃了(我假设是因为我没有实现move构造函数)。
  3. 对snprintf使用strlen (由@ArtemyVysotsky建议)对snprintf,允许我在Zone构造函数中使用初始化程序列表。
  4. 已实现的拷贝赋值操作符Zone &operator=(Zone &other)
  5. 实现的复制构造函数
  6. 已实现的移动赋值算子Zone &operator=(Zone &&other)
  7. 实现的移动构造函数

发现:每当我向向量添加一个元素时,。以前的元素被“复制”到新的容器位置,早期的元素被销毁。我希望它们能被移动而不是被复制。我不确定是否需要做些什么来确保它们被移动而不是复制。

Furher更新看起来是这样的,为了使用移动构造函数,它需要是noexcept。一旦我这样做,相同的代码,没有任何改变,现在使用移动而不是复制。

根据的建议编写工作代码

代码语言:javascript
复制
struct Zone {
    Zone() {}
    Zone(const char* name, const float zoneTemperature, const float zoneHumidity)
        :name(strcpy(new char[strlen(name) + 1], name))
        ,temperature{ zoneTemperature }
        ,humidity {zoneHumidity}
    {
        cout << "Zone constructor: " << name << endl;
    }
    /* Copy Constructor */
    Zone(Zone const& other)
        :name(strcpy(new char[strlen(other.name) + 1], other.name))
        ,temperature{ other.temperature }
        ,humidity{ other.humidity }
    {
        std::cout << "In Zone Copy Constructor. name = " << other.name << ". Copying resource." << std::endl;
    }
    /* Move Constructor */
    Zone(Zone&& other) noexcept
        : name(nullptr)
        , temperature(0.0f)
        , humidity(0.0f)
    {
        std::cout << "In Zone Move Constructor. name = "    << other.name << ". Moving resource." << std::endl;

        // Copy the data pointer and its length from the   
        // source object.  
        name = other.name;
        temperature = other.temperature;
        humidity = other.humidity;

        // Release the data pointer from the source object so that  
        // the destructor does not free the memory multiple times.  
        other.name = nullptr;
        other.temperature = 0.0f;
        other.humidity = 0.0f;
    }

    ~Zone()
    {
        cout << "Zone Destructor: " << name << endl;
        delete[] name;
    }

    /* Copy Assignment Operator */
    Zone& operator=(Zone const& other) {
        std::cout << "In Zone Copy Assignment Operator. name = " << other.name << "." << std::endl;
        Zone tmpZone(other);
        std::swap(name, tmpZone.name);
        std::swap(temperature, tmpZone.temperature);
        std::swap(humidity, tmpZone.humidity);
        return *this;
    }

    /* Move Assignment Operator */
    Zone& operator=(Zone&& other) noexcept {
        std::cout << "In Zone Move Assignment Operator. name = " << other.name << "." << std::endl;

        if (this != &other)
        {
            // Free the existing resource.  
            delete[] name;

            // Copy the data pointer and its length from the   
            // source object.  
            name = other.name;
            temperature = other.temperature;
            humidity = other.humidity;

            // Release the data pointer from the source object so that  
            // the destructor does not free the memory multiple times.  
            other.name = nullptr;
            other.temperature = 0.0f;
            other.humidity = 0.0f;
        }

        return *this;
    }

    char* name = nullptr;
    float temperature = 0.0f;
    float humidity = 0.0f;
};

class AvailableZones {
public:
    AvailableZones::AvailableZones() {
        m_zoneVec = std::vector<Zone>();
    }

    ~AvailableZones() {
    }

    /*
        Using Arguments, add a Zone to the private zoneVec member is it does not exist
        If is does exist (names of zones are unique and used as the "key"), then update
        the temperature and humidity of the existing zone with those in the arguments
    */
    void AvailableZones::upsertZone(const char *name, const float temperature, const float humidity) {

        for (auto &zone : m_zoneVec) {
            if (strcmp(zone.name, name) == 0) {
                zone.temperature = temperature;
                zone.humidity = humidity;
                return;
            }
        }       

        m_zoneVec.emplace_back(name, temperature, humidity);
    }

    /*
        Given a Zone name, find the zone and return it
        If a Zone with the given name does not exist
        return a nullptr
    */
    const Zone *AvailableZones::findZone(const char *name) const {

        for (auto const &zone : m_zoneVec) {
            if (strcmp(zone.name, name) == 0) {
                return &zone;
            }
        }

        return nullptr;
    }

private:
    std::vector<Zone> m_zoneVec;
};

void doWork() {
    static_assert(std::is_nothrow_move_constructible<Zone>::value, "Zone should be noexcept MoveConstructible");
    auto livingRoom = "Living Room";
    AvailableZones availableZones;
    availableZones.upsertZone("Master Bedroom", 72.0f, 50.0f);
    availableZones.upsertZone(livingRoom, 70.0f, 48.0f);
    availableZones.upsertZone("Study", 68.0f, 46.0f);

    auto foundZone = availableZones.findZone(livingRoom);
    cout << foundZone->name << endl;
    cout << foundZone->temperature << endl;
    cout << foundZone->humidity << endl;

    assert(strcmp(livingRoom, foundZone->name) == 0);
    assert(70.0f == foundZone->temperature);
    assert(48.0f == foundZone->humidity);

    availableZones.upsertZone(livingRoom, 74.0f, 52.0f);

    foundZone = availableZones.findZone(livingRoom);
    assert(strcmp(livingRoom, foundZone->name) == 0);
    assert(74.0f == foundZone->temperature);
    assert(52.0f == foundZone->humidity);

    foundZone = availableZones.findZone("Non Existent Zone");
    assert(foundZone == nullptr);
}


int main()
{
    doWork();
    return 0;
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-08-27 00:09:48

如果不检查返回的指针是否为nullptr,则从findZone()返回一个指针是没有用的(也是危险的)。

代码语言:javascript
复制
auto foundZone = availableZones.findZone(livingRoom);
cout << foundZone->name << endl;

findZone()解决问题有很多不同的方法;为了避免问题,我建议避免指针并返回元素的副本(但您必须编写副本构造函数);但是,如果您真的想返回一个指针,可以按照以下方式重写该函数

代码语言:javascript
复制
Zone const * findZone(const char *name) const {
    for ( auto const & zone : m_zoneVec) {
        if (strcmp(zone.name, name) == 0) {
            return & zone;
        }
    }

    return nullptr;
}

要点是使用const (因为该方法是const)对m_zoneVec中的元素(auto const & zone : m_zoneVec;观察&)中的元素使用引用,当您现在使用临时的E 115复制<代码>E 216 (auto zone : m_zoneVec;no &所以复制而不是引用)。因此,您可以返回向量元素的指针,而不是被立即销毁的临时对象的指针。

upsertZone()中也有同样的问题:循环测试和(在情况下)修改向量中元素的副本

代码语言:javascript
复制
    for (auto zone : m_zoneVec) {  // DANGER: zone is a **copy**
        if (strcmp(zone.name, name) == 0) {
            zone.temperature = temperature;
            zone.humidity = humidity;
            return;
        }
    }

因此,您可以修改立即销毁的副本;原始的Zone是不动的。

您必须修改引用

代码语言:javascript
复制
    for (auto & zone : m_zoneVec) {  // with & zone is a **reference**
        if (strcmp(zone.name, name) == 0) {
            zone.temperature = temperature;
            zone.humidity = humidity;
            return;
        }
    }

但是,非常重要的是,您应该创建一个副本构造函数(可能还有一个移动构造函数);一个副本构造函数,它为名称分配一个新数组(用new);否则,就会有一个默认的复制构造函数来复制指针。

所以,举个例子,当你写

代码语言:javascript
复制
m_zoneVec.push_back(Zone(name, temperature, humidity));

创建一个临时对象,然后通过创建副本和销毁临时对象将其推入矢量。如果启用了delete中的Zone析构函数,则销毁临时delete -- name和向量中的值--将使用指向空闲区域的name。从这一点开始,程序的行为是未定义的,而且无论如何,当availableZone被销毁时(在程序的末尾),就会在->崩溃之前删除的指针上调用delete

您可以使用emplace_back()避免插入副本(我建议这样做)

代码语言:javascript
复制
m_zoneVec.emplace_back(name, temperature, humidity);

但是在m_zoneVec中添加更多的元素可能会导致向量的重新定位,因此可以移动副本和销毁。

如果您可以使用std::unique_ptr,我想您也可以使用std::shared_ptr

显式复制和移动构造函数创建的一个可能的替代方法是使用插入到智能指针中的name (根据AvailableZones的使用是唯一的或共享的)。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/45898854

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档