C ++ 11 - 在结构体向量

时间:2017-08-26 19:15:21

标签: c++ c++11 for-loop embedded unique-ptr

我已经在其他地方和SO搜索过这样做的方法,但没有找到解决我的问题和疑虑的任何答案。

约束:

  1. 我在嵌入式设备上使用 C ++ 11
  2. 我无法使用 std::string
  3. 我无法使用 std::make_unique() (但我可以std::unique_ptr使用new
  4. 我无法使用 strcpy_s()
  5. 我遇到的问题

    我遇到的主要问题是,在AvailableZones::upsertZone方法中,我想在向量中添加区域(如果它尚未存在)(使用name参数作为a" key")。如果存在,我想更新区域的温度和湿度成员。 "添加"但是,部分工作,但更新部分没有。

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

    关注:

    作为C ++的新手,我非常确定我没有用正确的C ++ 11方式做很多事情。我打开(急切地)任何/所有指导。 在AvailableZones::findZone方法中,我希望返回Zone,而不是创建副本或使用new / malloc。看起来我需要使用常规for / while循环?我已经看到了一些迭代器代码,但它看起来令人困惑/复杂,但我不确定使用迭代器是否也能解决这个问题。

    最佳实践相关问题:

    1. Zone struct的析构函数中,如果我使用delete,则会导致 我运行代码时的异常。我显然做错了什么。
    2. Zone结构中,我可以将name成员设为std::unique_ptr吗?如果 又怎样?我尝试了很多方法,但我无法得到它 编译或工作。
    3. 是否有更好的方法来实现Zone构造函数?
    4. 守则

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

      #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()(早期代码)创建了一个临时副本,但只是被丢弃(我假设因为我没有实现移动构造函数)。
      3. 使用strlen(由@ArtemyVysotsky建议)对抗snprintf,允许我在Zone构造函数中使用初始化列表。
      4. 已实施复制分配运算符Zone &operator=(Zone &other)
      5. 已实施的复制构造函数
      6. 已实施移动分配运算符Zone &operator=(Zone &&other)
      7. 已实施移动构造函数
      8. 调查结果: 每次我向向量添加一个元素。以前的元素是&#34;复制&#34;到新的容器位置和早期的元素被破坏。我希望他们能够被移动而不是被复制。我不确定是否需要做些什么来确保它们被移动而不是复制。

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

        根据提出的建议工作代码

        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;
        }
        

1 个答案:

答案 0 :(得分:1)

如果您不检查返回的指针是否为nullptr,则从findZone()返回nullptr是没有用的(而且很危险)

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

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

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_zoneVecauto const & zone : m_zoneVec中的元素;观察&)现在您使用临时 副本auto zone : m_zoneVec;没有&,因此请复制而不是参考)。因此,您可以返回向量元素的指针,而不是立即销毁的临时对象的指针。

upsertZone()中存在完全相同的问题:循环测试和(以防)修改向量中元素的副本

    for (auto zone : m_zoneVec) {  // DANGER: zone is a **copy**
        if (strcmp(zone.name, name) == 0) {
            zone.temperature = temperature;
            zone.humidity = humidity;
            return;
        }
    }

所以你修改了立即销毁的副本;原始Zone未被触及。

您必须修改参考

    for (auto & zone : m_zoneVec) {  // with & zone is a **reference**
        if (strcmp(zone.name, name) == 0) {
            zone.temperature = temperature;
            zone.humidity = humidity;
            return;
        }
    }

但是,非常非常重要的是,您应该创建一个复制构造函数(也许是一个移动构造函数);一个复制构造函数,为名称分配(new)一个新数组;否则有复制指针的默认复制构造函数。

所以,例如,当你写

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

你创建了一个临时对象,你通过创建一个副本并将其推送到临时对象中。如果启用了delete析构函数中的Zone,那么临时delete name的破坏以及向量中的值正在使用name那个点到自由区。从这一点来看,程序的行为是未定义的,无论如何,当availableZone被销毁时(在程序结束时),delete被调用之前被删除的指针 - &gt ;崩溃!

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

m_zoneVec.emplace_back(name, temperature, humidity);

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

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

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